diff --git a/.env.sample b/.env.sample index f21de7484..79dd000ed 100644 --- a/.env.sample +++ b/.env.sample @@ -3,3 +3,5 @@ GATSBY_ALGOLIA_SEARCH_KEY= ALGOLIA_ADMIN_KEY= GATSBY_CLOUDFLARE_URL= BUILD_ENV=LOCAL + + diff --git a/modules/ROOT/pages/common/nav-embedding.adoc b/modules/ROOT/pages/common/nav-embedding.adoc index 347aa02bb..c65bdd872 100644 --- a/modules/ROOT/pages/common/nav-embedding.adoc +++ b/modules/ROOT/pages/common/nav-embedding.adoc @@ -124,6 +124,8 @@ Customize and integrate Embedding tutorials * link:{{navprefix}}/tutorials/tutorials-overview[Embedding tutorials] + +//// * link:{{navprefix}}/tutorials/tse-fundamentals/intro[Embedding Fundamentals] ** link:{{navprefix}}/tutorials/tse-fundamentals/lesson-01[01 - Overview] ** link:{{navprefix}}/tutorials/tse-fundamentals/lesson-02[02 - Set up for course] @@ -142,8 +144,16 @@ Embedding tutorials ** link:{{navprefix}}/tutorials/react-components/lesson-02[02 - ThoughtSpot component pages] ** link:{{navprefix}}/tutorials/react-components/lesson-03[03 - Menus and navigation elements] ** link:{{navprefix}}/tutorials/react-components/lesson-04[04 - Event handling] -* link:{{navprefix}}/tutorials/spotter/integrate-into-chatbot[Integrate Spotter into your Chatbot] +//* link:{{navprefix}}/tutorials/spotter/integrate-into-chatbot[Integrate Spotter into your Chatbot] + +* link:{{navprefix}}/tutorials/react-components/intro[Introduction] +** link:{{navprefix}}/tutorials/react-components/lesson-01[Initializing ThoughtSpot Embed SDK] +** link:{{navprefix}}/tutorials/react-components/lesson-02[ThoughtSpot component pages] +** link:{{navprefix}}/tutorials/react-components/lesson-03[Menus and other navigation elements] +** link:{{navprefix}}/tutorials/react-components/lesson-04[Event handling] +* link:{{navprefix}}/tutorials/spotter/integrate-into-chatbot[Integrate Spotter into your chatbot] +//// [.sidebar-title] Reference guides and changelog diff --git a/modules/ROOT/pages/common/nav-release-notes.adoc b/modules/ROOT/pages/common/nav-release-notes.adoc new file mode 100644 index 000000000..061b9ec4d --- /dev/null +++ b/modules/ROOT/pages/common/nav-release-notes.adoc @@ -0,0 +1,17 @@ + +:page-pageid: nav-release-notes +:page-description: Release notes and changelogs + +[navSection] + +[.sidebar-title] +Release notes and changelogs + +* link:{{navprefix}}/whats-new[What's new] +* Changelog +** link:{{navprefix}}/embed-sdk-changelog[Visual Embed SDK changelog] +** link:{{navprefix}}/mobile-sdk-changelog[Mobile Embed SDK changelog] +** link:{{navprefix}}/rest-v2-changelog[REST API v2 changelog] +** link:{{navprefix}}/mcp-server-changelog[Spotter MCP Server changelog] +* link:{{navprefix}}/deprecated-features[Deprecation announcements] + diff --git a/modules/ROOT/pages/common/nav-rest-api.adoc b/modules/ROOT/pages/common/nav-rest-api.adoc index b81241013..4831ba813 100644 --- a/modules/ROOT/pages/common/nav-rest-api.adoc +++ b/modules/ROOT/pages/common/nav-rest-api.adoc @@ -38,8 +38,6 @@ REST APIs ** link:{{navprefix}}/webhooks-rest-api[Webhook APIs] - - [.sidebar-title] REST API SDK @@ -50,14 +48,19 @@ REST API SDK * link:{{navprefix}}/rest-api-sdk-csharp[C# SDK] * link:{{navprefix}}/rest-apiv2-js[REST API v2.0 in JavaScript] + [.sidebar-title] REST API Tutorials * link:{{navprefix}}/tutorials/rest-api/intro[REST API Tutorials] + +//// * link:{{navprefix}}/tutorials/rest-api/lesson-01[01 - REST API overview] * link:{{navprefix}}/tutorials/rest-api/lesson-02[02 - Simple Python implementation] * link:{{navprefix}}/tutorials/rest-api/lesson-03[03 - Complex REST API workflows] +//// + [.sidebar-title] REST API v1 (DEPRECATED) diff --git a/modules/ROOT/pages/common/nav-tutorials.adoc b/modules/ROOT/pages/common/nav-tutorials.adoc new file mode 100644 index 000000000..5ebf5d5b7 --- /dev/null +++ b/modules/ROOT/pages/common/nav-tutorials.adoc @@ -0,0 +1,51 @@ + +:page-pageid: nav-tutorials +:page-description: Tutorials navigation + +[navSection] + +[.sidebar-title] +Embedding fundamentals + +* link:{{navprefix}}/tutorials/tse-fundamentals/intro[Introduction] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-01[Overview of ThoughtSpot Embedded] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-02[Setting up for the course] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-03[Security Setting] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-04[Start Coding] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-05[Embed Search] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-06[Embed Natural Language Search] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-07[Embed Liveboard] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-08[Embed Liveboard Visualization] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-09[Embed Application] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-10[Styling ThoughtSpot] +* link:{{navprefix}}/tutorials/tse-fundamentals/lesson-11[Summary and Next Steps] + +[.sidebar-title] +UI elements and style customization + +* link:{{navprefix}}/tutorials/style-customization/intro[Introduction] +* link:{{navprefix}}/tutorials/style-customization/step-01[Getting started] +* link:{{navprefix}}/tutorials/style-customization/step-02[Large component elements] +* link:{{navprefix}}/tutorials/style-customization/step-03[CSS styles] +* link:{{navprefix}}/tutorials/style-customization/step-04[Text strings and icon replacement] +* link:{{navprefix}}/tutorials/style-customization/step-05[Menu items] +* link:{{navprefix}}/tutorials/style-customization/step-06[Conclusion] + +[.sidebar-title] +REST API tutorials + +** link:{{navprefix}}/tutorials/rest-api/intro[Introduction] +** link:{{navprefix}}/tutorials/rest-api/lesson-01[REST API overview] +** link:{{navprefix}}/tutorials/rest-api/lesson-02[Simple Python implementation] +** link:{{navprefix}}/tutorials/rest-api/lesson-03[Complex REST API workflows] +** link:{{navprefix}}/tutorials/rest-api/lesson-04[Browser JavaScript REST API implementation] + +[.sidebar-title] +React Components Tutorial + +* link:{{navprefix}}/tutorials/react-components/intro[Introduction] +* link:{{navprefix}}/tutorials/react-components/lesson-01[Initializing ThoughtSpot Embed SDK] +* link:{{navprefix}}/tutorials/react-components/lesson-02[ThoughtSpot component pages] +* link:{{navprefix}}/tutorials/react-components/lesson-03[Menus and other navigation elements] +* link:{{navprefix}}/tutorials/react-components/lesson-04[Event handling] +//* link:{{navprefix}}/tutorials/spotter/integrate-into-chatbot[Integrate Spotter into your chatbot] diff --git a/modules/ROOT/pages/common/nav-walkthroughs.adoc b/modules/ROOT/pages/common/nav-walkthroughs.adoc new file mode 100644 index 000000000..86bcf1d09 --- /dev/null +++ b/modules/ROOT/pages/common/nav-walkthroughs.adoc @@ -0,0 +1,24 @@ + +:page-pageid: nav-walkthroughs +:page-description: Guided Walkthroughs navigation + +[.sidebar-title] +Get started + +* Build a Data-Driven App with ThoughtSpot Embedded +** link:{{navprefix}}/tutorials/embed-data-driven-app/intro[Introduction] +** link:{{navprefix}}/tutorials/embed-data-driven-app/step-00[Get into the Developer Portal] +** link:{{navprefix}}/tutorials/embed-data-driven-app/step-02[Get the base Liveboard rendering] +** link:{{navprefix}}/tutorials/embed-data-driven-app/step-03[Make it conversational] +** link:{{navprefix}}/tutorials/embed-data-driven-app/step-04[Style it to match the portal] +** link:{{navprefix}}/tutorials/embed-data-driven-app/step-05[Wire in a custom action] +** link:{{navprefix}}/tutorials/embed-data-driven-app/troubleshooting[When it doesn't just work] +** link:{{navprefix}}/tutorials/embed-data-driven-app/summary[Where to go from here] + +* Build It Faster: Embedding ThoughtSpot with SpotterCode +** link:{{navprefix}}/tutorials/spottercode-embed/intro[Introduction] +** link:{{navprefix}}/tutorials/spottercode-embed/step-01[Set up SpotterCode, your pair programmer] +** link:{{navprefix}}/tutorials/spottercode-embed/step-02[Embed the Liveboard view] +** link:{{navprefix}}/tutorials/spottercode-embed/step-03[Embed Spotter: Extend it to conversational analytics] +** link:{{navprefix}}/tutorials/spottercode-embed/step-04[Troubleshooting] +** link:{{navprefix}}/tutorials/spottercode-embed/summary[Where to go from here] diff --git a/modules/ROOT/pages/common/nav.adoc b/modules/ROOT/pages/common/nav.adoc index 688e73792..0dc98b8d2 100644 --- a/modules/ROOT/pages/common/nav.adoc +++ b/modules/ROOT/pages/common/nav.adoc @@ -5,15 +5,18 @@ [navSection] [.sidebar-title] -Release notes and changelogs +Hands-on Guides -* link:{{navprefix}}/whats-new[What's new] -* Changelog -** link:{{navprefix}}/embed-sdk-changelog[Visual Embed SDK changelog] -** link:{{navprefix}}/mobile-sdk-changelog[Mobile Embed SDK changelog] -** link:{{navprefix}}/rest-v2-changelog[REST API v2 changelog] -** link:{{navprefix}}/mcp-server-changelog[Spotter MCP Server changelog] -* link:{{navprefix}}/deprecated-features[Deprecation announcements] +* link:{{navprefix}}/tutorials/walkthroughs[Walkthroughs] + +[.sidebar-title] +Quickstarts + +* link:{{navprefix}}/getting-started[Embed ThoughtSpot] +* link:{{navprefix}}/SpotterCode[SpotterCode] +* link:{{navprefix}}/rest-apis[REST APIs] +* link:{{navprefix}}/ai-analytics-integration[AI analytics integration] +* link:{{navprefix}}/mcp-integration[MCP Server integration] [.sidebar-title] Live Playgrounds @@ -27,69 +30,61 @@ Live Playgrounds * +++Theme Builder+++ ** link:{{navprefix}}/theme-builder-doc[How to use] - [.sidebar-title] -Get started +Reference Guides -* link:{{navprefix}}/getting-started[Embed ThoughtSpot] -* link:{{navprefix}}/SpotterCode[SpotterCode] -* link:{{navprefix}}/rest-apis[REST APIs] -* link:{{navprefix}}/ai-analytics-integration[AI analytics integration] -* link:{{navprefix}}/mcp-integration[MCP Server integration] -//** link:{{navprefix}}/tutorials/tutorials-overview[Tutorials] * link:{{navprefix}}/VisualEmbedSdk[SDK and API reference] -** link:{{navprefix}}/VisualEmbedSdk[Visual Embed SDK] -** link:{{navprefix}}/rest-apiv2-reference[REST API] -** link:{{navprefix}}/mcp-tool-reference[MCP tools] +* link:{{navprefix}}/rest-apiv2-reference[REST API] +* link:{{navprefix}}/mcp-tool-reference[MCP tools] +[.sidebar-title] +Integration Guides + +* link:{{navprefix}}/webhooks-overview[Overview] +** link:{{navprefix}}/webhooks-ui[Webhooks UI] +** link:{{navprefix}}/webhooks-comm-channel[Webhook communication channels] +** link:{{navprefix}}/webhooks-lb-schedule[Webhook connection for Liveboard scheduled events] +** link:{{navprefix}}/webhooks-s3-integration[AWS S3 storage integration for webhook delivery] +** link:{{navprefix}}/webhooks-gcs-storage[GCS storage integration for webhook delivery] +** link:{{navprefix}}/webhooks-lb-payload[Webhook response payload] +** link:{{navprefix}}/webhooks-kpi[Webhook connection for KPI alerts] + +* link:{{navprefix}}/external-tool-script-integration[External tools and scripts] +* link:{{navprefix}}/pendo-integration[Pendo integration with embed] +* link:{{navprefix}}/sf-integration[Integration with Salesforce] +* link:{{navprefix}}/vercel-integration[Vercel integration] [.sidebar-title] -Build and deploy +Deployment guides -** link:{{navprefix}}/thoughtspot-objects[ThoughtSpot objects] -** link:{{navprefix}}/timezone-aware-filtering[Timezone-aware keywords and filters] -** link:{{navprefix}}/variables[Variables] -** link:{{navprefix}}/parameterize-metadata[Parameterize metadata] +* link:{{navprefix}}/thoughtspot-objects[ThoughtSpot objects] +* link:{{navprefix}}/timezone-aware-filtering[Timezone-aware keywords and filters] +* link:{{navprefix}}/variables[Variables] +* link:{{navprefix}}/parameterize-metadata[Parameterize metadata] * link:{{navprefix}}/development-and-deployment[Development and deployment] -** link:{{navprefix}}/deploy-with-tml-apis[Deploy with TML APIs] -*** link:{{navprefix}}/git-provider-integration[Git provider integration] -*** link:{{navprefix}}/modify-tml[TML modification] +* link:{{navprefix}}/deploy-with-tml-apis[Deploy with TML APIs] +** link:{{navprefix}}/git-provider-integration[Git provider integration] +** link:{{navprefix}}/modify-tml[TML modification] * link:{{navprefix}}/publish-data-overview[Publish content to Orgs] ** link:{{navprefix}}/publish-to-orgs[Publish objects to Orgs] * link:{{navprefix}}/git-integration[Deploy with GitHub APIs (legacy)] ** link:{{navprefix}}/git-configuration[Configure GitHub integration] ** link:{{navprefix}}/git-api[GitHub REST APIs] ** link:{{navprefix}}/guid-mapping[GUID mapping] - -[.sidebar-title] -Multi-tenancy - * link:{{navprefix}}/multi-tenancy[Overview] -* link:{{navprefix}}/orgs[Multi-tenancy with Orgs] +** link:{{navprefix}}/orgs[Multi-tenancy with Orgs] ** link:{{navprefix}}/orgs-api-op[Org administration] ** link:{{navprefix}}/multitenancy-within-an-org[Multi-tenancy within an Org] ** link:{{navprefix}}/single-tenant-data-models[Single-tenant data models with Orgs] * link:{{navprefix}}/tse-cluster[Cluster maintenance and upgrade] [.sidebar-title] -Webhooks +Tutorials -* link:{{navprefix}}/webhooks-overview[Overview] -* link:{{navprefix}}/webhooks-ui[Webhooks UI] -* link:{{navprefix}}/webhooks-comm-channel[Webhook communication channels] -* link:{{navprefix}}/webhooks-lb-schedule[Webhook connection for Liveboard scheduled events] -* link:{{navprefix}}/webhooks-s3-integration[AWS S3 storage integration for webhook delivery] -* link:{{navprefix}}/webhooks-gcs-storage[GCS storage integration for webhook delivery] -* link:{{navprefix}}/webhooks-lb-payload[Webhook response payload] -* link:{{navprefix}}/webhooks-kpi[Webhook connection for KPI alerts] +* link:{{navprefix}}/tutorials/tutorials-overview[Embedding tutorials] +* link:{{navprefix}}/tutorials/rest-api/intro[REST API Tutorials] -[.sidebar-title] -Integration with external tools -** link:{{navprefix}}/external-tool-script-integration[External tools and scripts] -** link:{{navprefix}}/pendo-integration[Pendo integration with embed] -** link:{{navprefix}}/sf-integration[Integration with Salesforce] -** link:{{navprefix}}/vercel-integration[Vercel integration] [.sidebar-title] Additional resources diff --git a/modules/ROOT/pages/style-customization.adoc b/modules/ROOT/pages/style-customization.adoc index 6fd53a71e..4b3288c8b 100644 --- a/modules/ROOT/pages/style-customization.adoc +++ b/modules/ROOT/pages/style-customization.adoc @@ -29,7 +29,7 @@ Custom CSS allows developers to override the default styles and UI element speci + To customize themes and variables in the CSS file, developers must know the basics of HTML and CSS framework and how to build custom themes. For more information, see xref:css-customization.adoc[Advanced customization with custom CSS]. -A xref:style-customization_tutorial.adoc[hands-on tutorial] is also available to learn how to test style customization capabilities using Visual Embed Playground. +A xref:style-customization_tutorial-intro.adoc[hands-on tutorial] is also available to learn how to test style customization capabilities using Visual Embed Playground. == Scope of customization diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-intro.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-intro.adoc new file mode 100644 index 000000000..a315a67d8 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-intro.adoc @@ -0,0 +1,62 @@ += Build a data-driven app with ThoughtSpot Embedded +:toc!: +:linkattrs: +:sectanchors: +:page-title: Build a Data-Driven App with ThoughtSpot Embedded +:page-pageid: embed-data-driven-app__intro +:page-description: A hands-on guide to embedding a ThoughtSpot Liveboard and Spotter in a host application, built around one real embedding project. + +A hands-on guide, built around one real embedding project. + +== The scenario + +Imagine you're a full-stack developer at SpotStay, a short-term rental company. +Your team runs an internal portal that hosts use every day to check bookings, occupancy, and maintenance status across their listings. +Right now, if a host wants to know why occupancy dropped for a listing, they file a ticket and wait for the data team to pull a report. + +[div videoContainer] +-- +video::./images/mktng-tutorials/frame-video.mp4[width=100%,options="autoplay,loop"] +-- + +*Your mandate*: give hosts a self-serve view inside the portal they already use. + +* Embed a Liveboard so hosts see booking volume, occupancy rate, and average daily rate at a glance. +* Embed Spotter so hosts can ask follow-up questions in plain language, such as "why did occupancy drop for our Austin listings this week?". Spotter answers without you building a custom query interface for every possible question. +* Style and lock it down so it feels like part of the portal, not a third-party iframe bolted on the side. + +[NOTE] +==== +Liveboard is an AI-augmented dashboard within ThoughtSpot. +==== + +This guide covers the build end to end, the same path you would take for your own app. It matches the SpotStay example on GitHub if you want to work through it in code. +== Before you start + +* Confirm access to a ThoughtSpot instance (trial or org instance) with Developer or Admin privilege. +* Add your app domain to the CSP and CORS allowlist (*Develop* > *Customizations* > *Security Settings*). +* Confirm you have content to embed: a connected data warehouse, a data model, and a Liveboard. +* Confirm Spotter is enabled on your instance. This is on by default for most Cloud instances. If you don't see it, ask your ThoughtSpot administrator to enable it. + +[NOTE] +==== +Trial users: sample data is pre-connected. No data setup is required. +==== + +== Steps in this tutorial + +* xref:build-data-driven-app-with-tse-step-00.adoc[Get into the Developer Portal] +//* xref:build-data-driven-app-with-tse-step-01.adoc[Set up the project] +* xref:build-data-driven-app-with-tse-step-02.adoc[Get the base dashboard rendering] +* xref:build-data-driven-app-with-tse-step-03.adoc[Make it conversational] +* xref:build-data-driven-app-with-tse-step-04.adoc[Style it to match the portal] +* xref:build-data-driven-app-with-tse-step-05.adoc[Wire in a custom action] +//* xref:build-data-driven-app-with-tse-step-06.adoc[Lock it down] +* xref:build-data-driven-app-with-tse-troubleshooting.adoc[When it doesn't just work] +* xref:build-data-driven-app-with-tse-summary.adoc[Where to go from here] + +++++ +
+Next → +
+++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-00.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-00.adoc new file mode 100644 index 000000000..44e86d766 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-00.adoc @@ -0,0 +1,191 @@ += Step 0: Get into the Developer Portal +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 0: Get into the Developer Portal +:page-pageid: embed-data-driven-app__step-00 +:page-description: Find the Developer Playground, AI Theme Builder, SpotterCode, and Security Settings inside the ThoughtSpot Developer Portal. + +== Developer Portal +Start in the Developer Portal, where every ID and configuration option you need lives. + +[div videoContainer] +-- +video::./images/mktng-tutorials/dev-section.mp4[width=100%,options="autoplay,loop"] +-- + +. Log in to ThoughtSpot. +. Click the *Develop* tab. ++ +[NOTE] +==== +The *Develop* tab is visible only to users with Developer or Admin privilege. +If you don't see it, ask your administrator to add you to a group with the *Has Developer privilege* permission. +==== + +From the Developer Portal you have access to: + +Developer Playground:: +An interactive sandbox where you configure the component you want to embed, see it render live, and copy out the exact SDK code. +This is where every step starts. + +AI Theme Builder:: +The same live-render-and-copy pattern, but for styling. + +SpotterCode:: +An AI coding assistant built into the Playground and IDE that can generate and refine embed code from a plain-language prompt, and help theme it too. + +Security Settings:: +Where you allowlist your portal domain for CSP and CORS. +Nothing renders without this. + +Customizations:: +Connectors, security settings, and webhooks for the broader ThoughtSpot instance. + +[IMPORTANT] +==== +*Why this step matters:* skipping the Playground and hand-writing IDs from old tickets or Slack messages is the single most common source of "why is my embed blank" bugs. +Grab every ID from the Playground, not from memory, and keep the terminology aligned with the ThoughtSpot objects you are embedding. +==== + +== Project setup + +Before you write a single line of embed code, four things need to be settled: the portal's domain needs to be allowlisted, you need the actual cluster hostname, you need to know which auth type you're building against, and you need the project files scaffolded. +Skipping them is the fastest way to spend an afternoon debugging a CORS error and blaming the code. + +=== 1. Allowlist the portal domain + +In the ThoughtSpot UI, an admin or developer-privileged user goes to *Develop* > *Customizations* > *Security Settings* and adds your portal's domain (for example, `+https://spotstay.com+`) to the CSP and CORS allowlists. +Without this, the browser blocks the embed regardless of how correct the code is. + +This is a one-time, admin-side setup step: it is not something the SDK or any tooling generates for you. +Do this first so you are not debugging a CORS error later and assuming it is your code. + +=== 2. Get the cluster hostname + +Your ThoughtSpot instance lives at a specific hostname. +For a Cloud instance, it looks like `spotstay.thoughtspot.cloud`. +It's visible in the browser URL bar whenever you're logged into the ThoughtSpot UI. It is also listed under *Develop* > *REST API Playground*, which shows the fully qualified host that any API call must target. +This is the value that goes into `thoughtSpotHost`. If you get it wrong, every embed on the page renders blank. + +=== 3. Choose an auth type + +The tutorial-friendly option is `AuthType.None`. +It prompts for a ThoughtSpot login at runtime and is fine for local development. +It is not what ships to hosts in production. + +The production-recommended pattern is Trusted Authentication: your own backend exchanges credentials for a token server-side, and hosts are signed in to the embed silently. No ThoughtSpot login screen, no separate ThoughtSpot license per user. +Setting this up means standing up a small token-issuing endpoint on your side. It's a separate build from the embed code itself, and worth planning for before this goes past a demo. + +See xref:embed-authentication.adoc[Authentication] for the full option set, `AuthType.None`, `AuthType.Basic`, `AuthType.TrustedAuthToken`, `AuthType.TrustedAuthTokenCookieless`, and SSO-based flows, and which fits your setup. + +=== 4. Create the project structure + +With the domain allowed, the hostname in hand, and an auth type picked, create a small project folder. +This matches the structure in the SpotStay example repo on GitHub: + +[source] +---- +spotstay-embed/ +├── index.html # Page shell + wrapper app navigation +├── app.js # All embedding code (SpotterEmbed, LiveboardEmbed, custom actions) +└── styles.css # Host app styling +---- + +Install the SDK: + +[source,shell] +---- +npm install @thoughtspot/visual-embed-sdk +---- + +In `app.js`, import the components you'll use: + +[source,javascript] +---- +import { + SpotterEmbed, + LiveboardEmbed, + AuthType, + init, + EmbedEvent, + HostEvent, + CustomActionTarget, + CustomActionPosition, +} from '@thoughtspot/visual-embed-sdk'; +---- + +[NOTE] +==== +For a throwaway prototype, a CDN import works as well: keep + + +---- + +`app.js` initializes the SDK once, near the top of the file. +Always call `init()` before calling `render()` on any embed component: + +[source,javascript] +---- +const tsURL = 'https://spotstay.thoughtspot.cloud'; + +init({ + thoughtSpotHost: tsURL, + authType: AuthType.None, // <1> +}); +---- +<1> `AuthType.None` is fine while you're iterating locally: you get a one-time login popup and the session holds until it expires. +Before this touches production, swap in a token-based `AuthType` such as `AuthType.TrustedAuthTokenCookieless`, so hosts are authenticated silently through your own backend, not a ThoughtSpot login popup. + +Then add tab-switching logic so the nav bar swaps embeds: + +[source,javascript] +---- +const container = document.getElementById('ts-embed'); + +document.querySelectorAll('.nav-btn').forEach((btn) => { + btn.addEventListener('click', () => { + document.querySelectorAll('.nav-btn').forEach((b) => b.classList.remove('active')); + btn.classList.add('active'); + container.innerHTML = ''; + btn.dataset.view === 'spotter' ? renderSpotter() : renderLiveboard(); + }); +}); + +renderLiveboard(); // default view on load +---- + +++++ +
+← Previous +Next → +
+++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-01.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-01.adoc new file mode 100644 index 000000000..7f41b78b8 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-01.adoc @@ -0,0 +1,145 @@ += Step 1: Set up the project +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 1: Set up the project +:page-pageid: embed-data-driven-app__step-01 +:page-description: Allowlist your portal domain, get the cluster hostname, choose an auth type, and scaffold the project files. + +Before you write a single line of embed code, four things need to be settled: the portal's domain needs to be allowlisted, you need the actual cluster hostname, you need to know which auth type you're building against, and you need the project files scaffolded. +Skipping them is the fastest way to spend an afternoon debugging a CORS error and blaming the code. + +== 1. Allowlist the portal domain + +In the ThoughtSpot UI, an admin or developer-privileged user goes to *Develop* > *Customizations* > *Security Settings* and adds your portal's domain (for example, `+https://spotstay.com+`) to the CSP and CORS allowlists. +Without this, the browser blocks the embed regardless of how correct the code is. + +This is a one-time, admin-side setup step: not something the SDK or any tooling generates for you. +Do this first, so you aren't debugging a CORS error later and assuming it's your code. + +== 2. Get the cluster hostname + +Your ThoughtSpot instance lives at a specific hostname. +For a Cloud instance, it looks like `spotstay.thoughtspot.cloud`. +It's visible in the browser URL bar whenever you're logged into the ThoughtSpot UI, and it's also listed under *Develop* > *REST API Playground*, which shows the fully qualified host any API call needs to target. +This is the value that goes into `thoughtSpotHost`. If you get it wrong, every embed on the page renders blank. + +== 3. Choose an auth type + +The tutorial-friendly option is `AuthType.None`. +It prompts for a ThoughtSpot login at runtime and is fine for local development. +It is not what ships to hosts in production. + +The production-recommended pattern is Trusted Authentication: your own backend exchanges credentials for a token server-side, and hosts are signed in to the embed silently. No ThoughtSpot login screen, no separate ThoughtSpot license per user. +Setting this up means standing up a small token-issuing endpoint on your side. It's a separate build from the embed code itself, and worth planning for before this goes past a demo. + +See xref:embed-authentication.adoc[Authentication] for the full option set, `AuthType.None`, `AuthType.Basic`, `AuthType.TrustedAuthToken`, `AuthType.TrustedAuthTokenCookieless`, and SSO-based flows, and which fits your setup. + +== 4. Create the project structure + +With the domain allowed, the hostname in hand, and an auth type picked, create a small project folder. +This matches the structure in the SpotStay example repo on GitHub: + +[source] +---- +spotstay-embed/ +├── index.html # Page shell + wrapper app navigation +├── app.js # All embedding code (SpotterEmbed, LiveboardEmbed, custom actions) +└── styles.css # Host app styling +---- + +Install the SDK: + +[source,shell] +---- +npm install @thoughtspot/visual-embed-sdk +---- + +In `app.js`, import the components you'll use: + +[source,javascript] +---- +import { + SpotterEmbed, + LiveboardEmbed, + AuthType, + init, + EmbedEvent, + HostEvent, +} from '@thoughtspot/visual-embed-sdk'; +---- + +[NOTE] +==== +For a throwaway prototype, the CDN import works just as well: swap the package import for a ` + + +---- + +`app.js` initializes the SDK once, near the top of the file. +Always call `init()` before calling `render()` on any embed component: + +[source,javascript] +---- +const tsURL = 'https://spotstay.thoughtspot.cloud'; + +init({ + thoughtSpotHost: tsURL, + authType: AuthType.None, // <1> +}); +---- +<1> `AuthType.None` is fine while you're iterating locally: you get a one-time login popup and the session holds until it expires. +Before this touches production, swap in a token-based `AuthType` such as `AuthType.TrustedAuthTokenCookieless`, so hosts are authenticated silently through your own backend, not a ThoughtSpot login popup. + +Then add tab-switching logic so the nav bar swaps embeds: + +[source,javascript] +---- +const container = document.getElementById('ts-embed'); + +document.querySelectorAll('.nav-btn').forEach((btn) => { + btn.addEventListener('click', () => { + document.querySelectorAll('.nav-btn').forEach((b) => b.classList.remove('active')); + btn.classList.add('active'); + container.innerHTML = ''; + btn.dataset.view === 'spotter' ? renderSpotter() : renderLiveboard(); + }); +}); + +renderLiveboard(); // default view on load +---- + +++++ +
+← Previous +Next → +
+++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-02.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-02.adoc new file mode 100644 index 000000000..ad1e14261 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-02.adoc @@ -0,0 +1,62 @@ += Step 2: Get the base Liveboard rendering +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 2: Get the base Liveboard rendering +:page-pageid: embed-data-driven-app__step-02 +:page-description: Embed the bookings Liveboard, and optionally a single visualization from it, using the Liveboard ID from the Developer Playground. + + +Your first real milestone is getting the bookings Liveboard to render inside the portal. Grab the Liveboard ID from the Developer Playground and drop it straight in. + +[#import-liveboard-embed] +== Step 1: Import the LiveboardEmbed component + +Find the file you want to embed the Liveboard in and import the `LiveboardEmbed` component: + +[source,javascript] +---- +import { LiveboardEmbed } from '@thoughtspot/visual-embed-sdk/react'; +---- + +[div videoContainer] +-- +video::./images/mktng-tutorials/liveboard.mp4[width=100%,options="autoplay,loop"] +-- + +[#render-liveboard] +== Step 2: Render the Liveboard + +First, decide which Liveboard you want to embed: + +. In the ThoughtSpot UI, go to *Develop* > *Visual Embed SDK Playground*. +. Select *Liveboard Embed*, then select the Liveboard you want to embed. +. The *Code snippet* section updates with the generated code. Copy it directly into your file, or extract only the Liveboard ID. + +[source,javascript] +---- +function renderLiveboard() { + const liveboardEmbed = new LiveboardEmbed(container, { + frameParams: { width: '100%', height: '100%' }, + liveboardId: '<%=liveboardGUID%>', // from the Playground + }); + + liveboardEmbed.on(EmbedEvent.LiveboardRendered, () => { + console.log('Liveboard rendered'); + }); + + liveboardEmbed.render(); +} +---- + +[div videoContainer] +-- +video::./images/mktng-tutorials/analytics.mp4[width=100%,options="autoplay,loop"] +-- + +++++ +
+← Previous +Next → +
+++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-03.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-03.adoc new file mode 100644 index 000000000..e54fe5f56 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-03.adoc @@ -0,0 +1,63 @@ += Step 3: Make it conversational +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 3: Make it conversational +:page-pageid: embed-data-driven-app__step-03 +:page-description: Embed Spotter below the Liveboard so hosts can ask follow-up questions in plain language. + +The Liveboard is up. +Now for the second half of the ask: hosts can ask follow-up questions in their own words instead of hunting through Liveboard filters for "why." + +Find the file in which you want to embed Spotter and import the `SpotterEmbed` component: + +[source,javascript] +---- +import { SpotterEmbed } from '@thoughtspot/visual-embed-sdk/react'; +---- + +[div videoContainer] +-- +video::./images/mktng-tutorials/spotter-embed-playground.mp4[width=100%,options="autoplay,loop"] +-- + +[#render-spotter] +== Step 2: Render Spotter + +First, decide which data model hosts will query. Then: + +. In the ThoughtSpot UI, go to *Develop* > *Visual Embed SDK Playground*. +. Select *Spotter Embed*, then select the data model you want. +. The *Code snippet* section updates with the generated code. Copy it directly into your file, or extract just the Model ID. + +[source,javascript] +---- +function renderSpotter() { + const spotterEmbed = new SpotterEmbed(container, { + frameParams: { width: '100%', height: '100%' }, + worksheetId: '', // or 'auto_mode', see the following note + }); + + spotterEmbed.on(EmbedEvent.Init, () => console.log('Spotter initializing…')); + spotterEmbed.on(EmbedEvent.Load, () => console.log('Spotter loaded')); + + spotterEmbed.render(); +} +---- + +[NOTE] +==== +If your instance has Auto mode enabled, you don't need to lock Spotter to a single model. Set `worksheetId: 'auto_mode'` and let Spotter pick the right data source per question. +==== + +[div videoContainer] +-- +video::./images/mktng-tutorials/working-spotter-embed.mp4[width=100%,options="autoplay,loop"] +-- + +++++ +
+← Previous +Next → +
+++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-04.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-04.adoc new file mode 100644 index 000000000..5acf0e109 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-04.adoc @@ -0,0 +1,54 @@ += Step 4: Style it to match the portal +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 4: Style it to match the portal +:page-pageid: embed-data-driven-app__step-04 +:page-description: Use the AI Theme Builder to generate CSS variables and content strings that make the embed match the SpotStay portal. + +A Liveboard that looks like a different product breaks the self-serve, built-in feeling you are going for. +Start in the AI Theme Builder (*Develop* > *Customizations* > *Theme Builder*). Upload brand guidelines or describe them, generate a theme, and then fine-tune colors in Manual Mode. + +[div videoContainer] +-- +video::./images/mktng-tutorials/theme-builder-styling.mp4[width=100%,options="autoplay,loop"] +-- + +Exporting gives you a set of CSS variables that you can drop straight into `init()`: + +[source,javascript] +---- +init({ + thoughtSpotHost: tsURL, + authType: AuthType.None, + customizations: { + style: { + customCSS: { + variables: { + '--ts-var-button--primary-background': '#0B5FFF', + '--ts-var-root-color': '#1A1A1A', + '--ts-var-root-background': '#FFFFFF', + '--ts-var-spotter-prompt-background': '#F0F4FF', + }, + }, + }, + content: { + strings: { + 'Spotter': 'Ask SpotStay', // matches the nav button copy + }, + }, + }, +}); +---- + +[div videoContainer] +-- +video::./images/mktng-tutorials/themed-app.mp4[width=100%,options="autoplay,loop"] +-- + +++++ +
+← Previous +Next → +
+++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-05.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-05.adoc new file mode 100644 index 000000000..0f2f19af9 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-05.adoc @@ -0,0 +1,67 @@ += Step 5: Add a custom action +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 5: Add a custom action +:page-pageid: embed-data-driven-app__step-05 +:page-description: Add a custom action so hosts can escalate a listing straight from the Liveboard into your internal ticketing tool. + +Your team does not want to only look at the data. +When a host spots a listing with a maintenance issue dragging down occupancy, they want to escalate it into the internal ticketing tool without leaving the portal. +That is a custom action. + +Modify the `renderLiveboard` function to include the custom action: + +[source,javascript] +---- +function renderLiveboard() { + const liveboardEmbed = new LiveboardEmbed(container, { + frameParams: { width: '100%', height: '100%' }, + liveboardId: '', + customActions: [ + { + name: 'Open listing', + id: 'open-listing', + target: CustomActionTarget.VIZ, + position: CustomActionPosition.PRIMARY, + }, + ], + }); + + liveboardEmbed.on(EmbedEvent.LiveboardRendered, () => console.log('Liveboard rendered')); + + liveboardEmbed.on(EmbedEvent.CustomAction, (payload) => { + if (payload.data.id === 'open-listing') { + handleCustomAction(payload.data); + } + }); + + liveboardEmbed.render(); +} + +function handleCustomAction(data) { + const panel = document.getElementById('detail-panel'); + panel.classList.remove('hidden'); + panel.innerHTML = `
${JSON.stringify(data, null, 2)}
`; + // In production: POST this payload to your ticketing API +} +---- + +`CustomActionTarget` determines where the button appears — `VIZ`, `LIVEBOARD`, `SPOTTER`, or `ANSWER`. `CustomActionPosition` determines whether it appears in the primary toolbar or the overflow menu. + +[NOTE] +==== +If you only want this action visible on the bookings Liveboard and not every Liveboard in the instance, use `metadataIds`, `groupId`, and `orgId` to scope it further. +==== + +[div videoContainer] +-- +video::./images/mktng-tutorials/cbca.mp4[width=100%,options="autoplay,loop"] +-- + +++++ + +++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-06.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-06.adoc new file mode 100644 index 000000000..5134d12c4 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-step-06.adoc @@ -0,0 +1,37 @@ += Step 6: Lock it down +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 6: Lock it down +:page-pageid: embed-data-driven-app__step-06 +:page-description: Hide and disable ThoughtSpot actions that don't make sense for external, non-power-user hosts. + +Hosts aren't ThoughtSpot power users, and you don't want them downloading raw exports or seeing sharing options that don't map to anything in your portal. + +[source,javascript] +---- +import { + LiveboardEmbed, + Action, // <1> +} from '@thoughtspot/visual-embed-sdk'; + +const liveboardEmbed = new LiveboardEmbed(container, { + frameParams: { width: '100%', height: '100%' }, + liveboardId: 'YOUR_LIVEBOARD_GUID', + hiddenActions: [Action.Download, Action.Share], + disabledActions: [Action.SpotIQAnalyze], + disabledActionReason: 'Not available on SpotStay accounts — contact IT to enable.', +}); +---- +<1> `Action` must be imported from the SDK. + +`disabledActions` keeps the action visible but greyed out, with `disabledActionReason` as the tooltip explaining why. +`hiddenActions` removes the action from the UI entirely. +The full list of targetable actions is in the xref:Action.adoc[Action enum reference]. Worth a skim before deciding what to hide vs disable, since the two send very different signals to the end-user. + +++++ + +++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-summary.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-summary.adoc new file mode 100644 index 000000000..6381c7e30 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-summary.adoc @@ -0,0 +1,62 @@ += Where to go from here +:toc!: +:linkattrs: +:sectanchors: +:page-title: Where to go from here — Build a Data-Driven App with ThoughtSpot Embedded +:page-pageid: embed-data-driven-app__summary +:page-description: Wrap-up, next steps, and reference appendices for the SpotStay data-driven app embedding build. + +Two things are now in place: a host-scoped Liveboard and a conversational Spotter panel underneath it, both themed to match the SpotStay portal and locked down so hosts cannot download or share things that should not leave the tool. +The pattern underneath does not change for whatever you build next — a Liveboard for a different team, a Spotter embed inside a mobile app, or a custom action that writes back to a different internal system. +The full working version of this build is in the SpotStay example repo on GitHub if you want to see it end to end. + +Every build follows the same loop: + +. Grab the exact ID (Liveboard, model, or visualization) from the Developer Playground. Don't hand-type it. +. Wire it into your existing app shell with the SDK. +. Style it with CSS variables first. Use `rules_UNSTABLE` only when you have to. +. Lock down actions before it reaches real users, not after. + +== Next steps + +* xref:embed-spotter.adoc[Embed Spotter]: full reference +* xref:embed-pinboard.adoc[Embed a Liveboard]: full reference +* xref:custom-actions-callback.adoc[Code-based custom actions] +* xref:css-customization.adoc[CSS customization framework] +* xref:embed-authentication.adoc[Authentication options] +* xref:EmbedEvent.adoc[Embed events reference] +* link:https://github.com/thoughtspot/developer-examples[Developer examples on GitHub, window=_blank] + +[#appendix-a] +== Appendix A: Development environment + +* Use the latest Chrome build and keep DevTools open while developing: most embed issues show up as console errors first. +* Disable the browser cache while iterating (*DevTools* > *Network* > *Disable cache*), or you'll keep debugging a stale build of `app.js`. +* Blank embed → check the console, then confirm `thoughtSpotHost` is correct and reachable. + +[#appendix-b] +== Appendix B: Security settings + +Configure all three settings under *Develop* > *Customizations* > *Security Settings* before anything renders on your domain: + +CSP visual embed hosts:: +The domain(s) allowed to embed ThoughtSpot. For example, `+http://localhost:8000+`. +Use `*` only for local testing, never in production. + +CSP connect-src domains:: +Required for custom actions. The full URL (protocol and port) that a custom action or callback needs to reach. + +CORS allowlisted domains:: +Required with a token-based `AuthType`. The full URL of your embedding app. + +[NOTE] +==== +In production, your app's domain typically needs to be in all three lists. +==== + +++++ + +++++ diff --git a/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-troubleshooting.adoc b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-troubleshooting.adoc new file mode 100644 index 000000000..ef4d6ff80 --- /dev/null +++ b/modules/tutorials/pages/embed-data-driven-app/build-data-driven-app-with-tse-troubleshooting.adoc @@ -0,0 +1,36 @@ += When it doesn't just work +:toc!: +:linkattrs: +:sectanchors: +:page-title: When it doesn't just work — Build a Data-Driven App with ThoughtSpot Embedded +:page-pageid: embed-data-driven-app__troubleshooting +:page-description: Troubleshooting patterns for blank embeds, styling that doesn't apply, and custom actions with unexpected payloads. + +It is worth including in the guide on purpose because it happens on every real project. + +== Scenario: blank embed, no errors in the UI + +This is almost always a CSP issue. +Confirm the portal's domain is in the CSP visual embed hosts list (see xref:build-data-driven-app-with-tse-summary.adoc#appendix-b[Appendix B]), then check the browser console — most embed issues surface there first. + +== Scenario: styling changes don't apply + +Check whether you're using a CSS variable or a `rules_UNSTABLE` selector. +Selectors can silently stop matching after a ThoughtSpot release. CSS variables are the more durable option and should be the default choice unless you need something a variable can't reach. + +== Scenario: custom action fires but the payload looks wrong + +Log `payload.data` in full before assuming the SDK is broken. It's usually a target or position mismatch (for example, an action scoped to `VIZ` that you expected on `LIVEBOARD`). + +== General checklist + +* No response or blank frame → check the CSP allowlist and `thoughtSpotHost` first. +* Stale behavior after a code change → disable the browser cache (*DevTools* > *Network* > *Disable cache*) before debugging further. +* Something looks right in the Playground but wrong in your app → check what parameters your app passes versus what the Playground generated. + +++++ + +++++ diff --git a/modules/tutorials/pages/guided-walkthroughs.adoc b/modules/tutorials/pages/guided-walkthroughs.adoc new file mode 100644 index 000000000..5df59c5d1 --- /dev/null +++ b/modules/tutorials/pages/guided-walkthroughs.adoc @@ -0,0 +1,33 @@ += Walkthroughs +:toc!: + +:page-title: Walkthroughs +:page-pageid: walkthroughs +:page-description: ThoughtSpot hands-on guides for building an embedded data app + +These self-paced, hands-on guides walk you through building an app with ThoughtSpot Embedded, and through using SpotterCode to embed ThoughtSpot in your application. + +[.non-link] +++++ +
+
+
+
+
Build faster with SpotterCode
+

Learn how to build faster and accelerate embedding ThoughtSpot Liveboards and Spotter in your app using SpotterCode, ThoughtSpot's AI coding assistant.

+
+

Start

+
+
+
+
+
+
Build a data-driven embedded app
+

A hands-on guide to embedding a ThoughtSpot Liveboard and Spotter in a host application, built around one real embedding project.

+
+
+
+
+
+++++ diff --git a/modules/tutorials/pages/react-components/react-components_intro.adoc b/modules/tutorials/pages/react-components/react-components_intro.adoc index de5cfe70e..611068d3a 100644 --- a/modules/tutorials/pages/react-components/react-components_intro.adoc +++ b/modules/tutorials/pages/react-components/react-components_intro.adoc @@ -1,6 +1,5 @@ = ThoughtSpot React Components Tutorial -:toc: true -:toclevels: 2 +:toc!: :page-pageid: react-components__intro :description: This is a self-guided course on the ThoughtSpot Visual Embed SDK React Components @@ -207,7 +206,8 @@ Again, this is simply to provide a simple example of how you will integrate rout === Other subdirectories and files The following lessons will cover the other files and the subdirectory structure used within the app to properly use the ThoughtSpot React components. -''' - -xref:react-components_lesson-01.adoc[Next: 01 - Initializing ThoughtSpot Embed SDK >] - +++++ + +++++ diff --git a/modules/tutorials/pages/react-components/react-components_lesson-01.adoc b/modules/tutorials/pages/react-components/react-components_lesson-01.adoc index 572cbcc5e..c7869dac0 100644 --- a/modules/tutorials/pages/react-components/react-components_lesson-01.adoc +++ b/modules/tutorials/pages/react-components/react-components_lesson-01.adoc @@ -1,6 +1,5 @@ = Initializing ThoughtSpot Embed SDK -:toc: true -:toclevels: 2 +:toc!: :page-pageid: react-components__lesson-01 :description: Initializing ThoughtSpot Embed SDK within React app @@ -92,7 +91,9 @@ You'll notice in the app options for some of the most common set of configuratio } ---- -''' - -xref:react-components_intro.adoc[< Previous: Intro] | xref:react-components_lesson-02.adoc[Next: 02 - ThoughtSpot component pages >] - +++++ + +++++ diff --git a/modules/tutorials/pages/react-components/react-components_lesson-02.adoc b/modules/tutorials/pages/react-components/react-components_lesson-02.adoc index 6429ee6be..ac5bc392d 100644 --- a/modules/tutorials/pages/react-components/react-components_lesson-02.adoc +++ b/modules/tutorials/pages/react-components/react-components_lesson-02.adoc @@ -1,8 +1,7 @@ = ThoughtSpot component pages :page-pageid: react-components__lesson-02 :description: Build a page that returns a ThoughtSpot component -:toc: true -:toclevels: 2 +:toc!: == app/dashboard/[dashboardId]/page.tsx The link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/app/dashboard/%5BdashboardId%5D/page.tsx[first page we will build^] uses the `LiveboardEmbed` component to display any Liveboard, if passed an ID that matches existing content. @@ -153,7 +152,9 @@ The React components have the same properties available as *props* of the compon The use of *props* for configuration options and xref:react-components_lesson-04.adoc[event handlers] is the biggest difference between the React components and the JavaScript Visual Embed SDK. - -''' - -xref:react-components_lesson-01.adoc[< Previous: 01 - Initializing ThoughtSpot Embed SDK] | xref:react-components_lesson-03.adoc[Next: 03 - Menus and other navigation elements >] +++++ + +++++ diff --git a/modules/tutorials/pages/react-components/react-components_lesson-03.adoc b/modules/tutorials/pages/react-components/react-components_lesson-03.adoc index 9baf18f7b..5ebb5ee83 100644 --- a/modules/tutorials/pages/react-components/react-components_lesson-03.adoc +++ b/modules/tutorials/pages/react-components/react-components_lesson-03.adoc @@ -1,8 +1,7 @@ = Menus and other navigation elements :page-pageid: react-components__lesson-03 :description: Use REST APIs to create menus and other navigation elements -:toc: true -:toclevels: 2 +:toc!: The ThoughtSpot component page from the previous lesson was designed to display any Liveboard with a valid ID as part of the URL. @@ -249,6 +248,9 @@ Spotter conversations are started against Models, so the set of UI components an The basic concepts from the entire tutorial apply regardless of which component you are using. -''' - -xref:react-components_lesson-02.adoc[< Previous: 02 - ThoughtSpot component pages] | xref:react-components_lesson-04.adoc[Next: 04 - Event handling >] +++++ + +++++ diff --git a/modules/tutorials/pages/react-components/react-components_lesson-04.adoc b/modules/tutorials/pages/react-components/react-components_lesson-04.adoc index 15b0905ae..b864e5870 100644 --- a/modules/tutorials/pages/react-components/react-components_lesson-04.adoc +++ b/modules/tutorials/pages/react-components/react-components_lesson-04.adoc @@ -1,8 +1,7 @@ = Event handling :page-pageid: react-components__lesson-04 :description: React components attach event handlers differently than the JavaScript Visual Embed SDK -:toc: true -:toclevels: 2 +:toc!: The ThoughtSpot Visual Embed SDK provides a set of Embed events that can be listened to and handled via callback functions. @@ -44,6 +43,9 @@ The `embedRef.current` syntax allows for issuing the `.trigger()` method of call You can use the same syntax to link other UI elements within the React page to the ThoughtSpot component without generating unnecessary and disruptive re-rendering. -''' - -xref:react-components_lesson-03.adoc[< Previous: 03 - Menus and other navigation elements] +++++ + +++++ diff --git a/modules/tutorials/pages/rest-api/rest-api-intro.adoc b/modules/tutorials/pages/rest-api/rest-api-intro.adoc index 50cc0aa39..a1857d2c6 100644 --- a/modules/tutorials/pages/rest-api/rest-api-intro.adoc +++ b/modules/tutorials/pages/rest-api/rest-api-intro.adoc @@ -1,6 +1,5 @@ = ThoughtSpot REST API tutorials -:toc: true -:toclevels: 1 +:toc!: :page-title: ThoughtSpot REST API tutorials :page-pageid: rest-api__intro @@ -40,6 +39,8 @@ image:images/tutorials/rest-api/open-tutorial-folder.png[Open folder in IDE] Now you're ready to begin the tutorial lessons. -''' - -xref:rest-api_lesson-01.adoc[Next: 01 - REST API Overview >] +++++ + +++++ diff --git a/modules/tutorials/pages/rest-api/rest-api_lesson-01.adoc b/modules/tutorials/pages/rest-api/rest-api_lesson-01.adoc index bf28715da..dfb69b6cb 100644 --- a/modules/tutorials/pages/rest-api/rest-api_lesson-01.adoc +++ b/modules/tutorials/pages/rest-api/rest-api_lesson-01.adoc @@ -1,6 +1,5 @@ = What is a REST API? -:toc: true -:toclevels: 3 +:toc!: :page-pageid: rest-api__lesson-01 @@ -147,6 +146,9 @@ Note that the model and example will only be a singular form of the object that If you look at the image above, the response is described as an `Array`, and so would be a JSON array containing any number of the `User` model from the example. -''' - -xref:rest-api-intro.adoc[< Back: REST API tutorial intro] | xref:rest-api_lesson-02.adoc[Next: 02 - Simple Python Implementation of V2.0 REST API >] +++++ + +++++ diff --git a/modules/tutorials/pages/rest-api/rest-api_lesson-02.adoc b/modules/tutorials/pages/rest-api/rest-api_lesson-02.adoc index 5e08236a5..09465f8d9 100644 --- a/modules/tutorials/pages/rest-api/rest-api_lesson-02.adoc +++ b/modules/tutorials/pages/rest-api/rest-api_lesson-02.adoc @@ -1,6 +1,5 @@ = Simple Python implementation of V2.0 REST API -:toc: true -:toclevels: 3 +:toc!: :page-pageid: rest-api__lesson-02 :description: A lesson on a simple implementation of the V2.0 using Python @@ -315,6 +314,9 @@ You may have noticed many steps that are repeated each time for any given reques In the next lesson, we'll cover using a *library* that wraps most of these repeated steps, so that you can focus simply on the logic of your API workflows. -''' - -xref:rest-api_lesson-01.adoc[< Previous: 01 - What is a REST API?] | xref:rest-api_lesson-03.adoc[Next: 03 - Complex REST API Workflows >] +++++ + +++++ diff --git a/modules/tutorials/pages/rest-api/rest-api_lesson-03.adoc b/modules/tutorials/pages/rest-api/rest-api_lesson-03.adoc index a201ea354..a205c4325 100644 --- a/modules/tutorials/pages/rest-api/rest-api_lesson-03.adoc +++ b/modules/tutorials/pages/rest-api/rest-api_lesson-03.adoc @@ -2,8 +2,7 @@ :page-pageid: rest-api__lesson-03 :description: Complex REST API Workflows -:toc: true -:toclevels: 1 +:toc!: == Get started The files for this tutorial are `api_training_python_2_begin.py` and `api_training_python_2_end.py`. @@ -372,6 +371,9 @@ By moving hard-coded values into variables, you can develop reusable scripts to There are many link:https://github.com/thoughtspot/thoughtspot_rest_api_python/tree/main/examples_v2[existing examples^] of workflows that can be pieced together to accomplish any number of administration and integration tasks. -''' - -xref:rest-api_lesson-02.adoc[< Back: 02 - Simple Python implementation of V2.0 REST API] | xref:rest-api_lesson-04.adoc[Next: 04 - Browser JavaScript REST API implementation >] +++++ + +++++ diff --git a/modules/tutorials/pages/rest-api/rest-api_lesson-04.adoc b/modules/tutorials/pages/rest-api/rest-api_lesson-04.adoc index abe1b6e96..effe6ca22 100644 --- a/modules/tutorials/pages/rest-api/rest-api_lesson-04.adoc +++ b/modules/tutorials/pages/rest-api/rest-api_lesson-04.adoc @@ -1,6 +1,5 @@ = 04 - Browser JavaScript REST API implementation -:toc: true -:toclevels: 1 +:toc!: :page-pageid: rest-api__lesson-04 :description: Browser JavaScript REST API implementation @@ -308,6 +307,9 @@ There is a link:https://github.com/thoughtspot/rest-api-sdk/tree/release/sdks/ty It is up to you to determine if the extra rigor and structure of the Typescript REST API SDK is helpful in the projects you build, or if a simple light implementation of a few of the REST API calls within a simple set of functions will suffice. -''' - -xref:rest-api_lesson-03.adoc[< Back: 03 - Complex Workflows in Python ] +++++ + +++++ diff --git a/modules/tutorials/pages/spotter/spotter-in-custom-chatbot.adoc b/modules/tutorials/pages/spotter/spotter-in-custom-chatbot.adoc index 8385d86b4..6156b7802 100644 --- a/modules/tutorials/pages/spotter/spotter-in-custom-chatbot.adoc +++ b/modules/tutorials/pages/spotter/spotter-in-custom-chatbot.adoc @@ -1,6 +1,5 @@ = Integrate Spotter into your chatbot -:toc: true -:toclevels: 1 +:toc!: :page-title: Integrate Spotter into your own Chatbot :page-pageid: spotter__integrate-into-chatbot diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-intro.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-intro.adoc new file mode 100644 index 000000000..50b26dafb --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-intro.adoc @@ -0,0 +1,116 @@ += Build it faster: embedding ThoughtSpot with SpotterCode +:toc!: +:linkattrs: +:sectanchors: +:page-title: Build it faster: Embedding ThoughtSpot with SpotterCode +:page-pageid: spottercode-embed__intro +:page-description: Use SpotterCode, ThoughtSpot's AI coding assistant, to accelerate embedding ThoughtSpot Liveboards and Spotter in your application. + + +You’re a full-stack developer at SpotStay, a short-term rental platform. +Your goal is to give hosts a self-serve analytics view inside the SpotStay host portal, something hosts can open to see how their listings are performing, without filing a support ticket or waiting on a data team. + +[div videoContainer] +-- +video::./images/mktng-tutorials/frame-video.mp4[width=100%,options="autoplay,loop"] +-- + + +Two things are on your list: + +* Embed a Liveboard so hosts can see occupancy, revenue, and booking trends for their own listings. +* Embed Spotter so hosts can ask follow-up questions in plain language, such as “why did my weekend bookings drop in March?”, without you building a custom query interface. + +You already know the Visual Embed SDK. What you don’t want to do is keep tabbing out to documentation for every parameter, event, and configuration option. That’s the problem SpotterCode solves for any embedding task. + +SpotterCode is the ThoughtSpot developer agent, purpose-built for embedded use cases. It answers from the current ThoughtSpot documentation and generates production-ready code tailored to your framework, so you spend less time moving between documentation and your IDE. + +[NOTE] +==== +Liveboard is an AI-augmented dashboard within ThoughtSpot. +==== + +[#before-you-start] +== Before you start: configure your ThoughtSpot instance + +Before any embed renders, two things must be true: your SpotStay portal domain is allowed to connect, and the SDK knows which ThoughtSpot cluster to use. Skipping this step is the most common reason a first embed shows a blank frame instead of a Liveboard. + +[#allowlist-domain] +=== 1. Allowlist the portal domain + +In the ThoughtSpot UI, an admin or developer-privileged user navigates to *Develop* > *Customizations* > *Security Settings* and adds the SpotStay portal domain (for example, `+https://portal.spotstay.com+`) to the CSP and CORS allowlists. Without this, the browser blocks the embed regardless of how correct the code is. + +[NOTE] +==== +This is a one-time setup step. It's an admin and configuration task, not something SpotterCode generates. Complete this before you write a single line of embed code, so you aren't debugging a CORS error and assuming it's your code. +==== + +[#get-cluster-hostname] +=== 2. Get the cluster hostname + +Your SpotStay ThoughtSpot instance lives at a specific hostname. For a Cloud instance, it looks like `spotstay.thoughtspot.cloud`. If you're not sure where to find it, ask SpotterCode: + +[source,text] +---- +"How do I find the hostname of my ThoughtSpot application instance?" +---- + +[#choose-auth-type] +=== 3. Choose an authentication type + +The tutorial-friendly option is `AuthType.None`: it prompts for a ThoughtSpot login at runtime and is fine for local development. It is not what ships to production. Before you decide, ask SpotterCode to walk you through the real options: + +[source,text] +---- +"What authentication types does the Visual Embed SDK support, and which one is recommended for a production embed with external users?" +---- + +Here's what you'll get back: `AuthType.None` is for testing only. Trusted Authentication (server-side token exchange) is the recommended pattern for production. It suits external, non-ThoughtSpot-licensed users such as SpotStay's hosts. + +[#initialize-sdk] +=== 4. Initialize the SDK once, at the app root + +Prompt for the `init` call before touching any individual embed component: + +[source,text] +---- +"Show me how to initialize the Visual Embed SDK in a React app using Trusted Authentication, with a fallback note on AuthType.None for local development." +---- + +[source,javascript] +---- +// Framework-agnostic. Call this once at your app entry point (React, Vue, or vanilla JS). + +import { init, AuthType } from '@thoughtspot/visual-embed-sdk'; + +init({ + thoughtSpotHost: 'https://spotstay.thoughtspot.cloud', + authType: AuthType.TrustedAuthTokenCookieless, // <1> + // authType: AuthType.None, // <2> +}); +---- +<1> Production: server-issued token. See xref:embed-authentication.adoc[Authentication] for setup. +<2> Local development only. Do not ship this. + +== Steps in this tutorial + +. xref:build-embed-app-with-spottercode-step-01.adoc[Set up SpotterCode, your pair programmer] +. xref:build-embed-app-with-spottercode-step-02.adoc[Embed the Liveboard view] +. xref:build-embed-app-with-spottercode-step-03.adoc[Embed Spotter: extend it with conversational analytics] +. xref:build-embed-app-with-spottercode-step-04.adoc[Troubleshooting] +. xref:build-embed-app-with-spottercode-summary.adoc[Where to go from here] + +//// +. xref:build-embed-app-with-spottercode-step-05.adoc[Summary] +. xref:build-embed-app-with-spottercode-step-06.adoc[Wire it to real data] +. xref:build-embed-app-with-spottercode-step-07.adoc[When it doesn't just work] +//// + + + + +++++ + +++++ diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-01.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-01.adoc new file mode 100644 index 000000000..f529bd5ff --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-01.adoc @@ -0,0 +1,67 @@ += Step 1: Set up SpotterCode, your pair programmer +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 1: Set up SpotterCode in your IDE +:page-pageid: spottercode-embed__step-01 +:page-description: Connect SpotterCode to your IDE via MCP and verify it has access to the ThoughtSpot developer docs and REST API reference. + +This guide uses a Claude Code environment as an example. The setup takes one of two paths, depending on your Claude account. + +[#path-claude-web] +=== If you have Claude AI (web) access + +. In Claude AI, go to *Customize* > *Connectors*. +. Click the *+* icon and select *Add custom connector*. +. Enter the server URL: ++ +[source,text] +---- +https://spottercode.thoughtspot.app/mcp +---- +. Click *Add*. ++ +This syncs to Claude Code automatically; no separate configuration is needed. + +[#path-claude-code-only] +=== If you have Claude Code only + +Run the following command from the CLI in your project directory: + +[source,bash] +---- +claude mcp add --transport http SpotterCode https://spottercode.thoughtspot.app/mcp +---- + +[#verify-connection] +=== Verify the connection + +Open a chat in your project and ask a scoped question to confirm SpotterCode is connected: + +[source,text] +---- +"Use SpotterCode to confirm you have access to the ThoughtSpot developer docs and REST API reference." +---- + +[div videoContainer] +-- +video::./images/mktng-tutorials/spottercode-connector.mp4[width=100%,options="autoplay,loop"] +-- + + +[#other-editors] +=== Cursor and VS Code + +The same pattern applies in Cursor or VS Code: + +* *Cursor*: One-click install from the Cursor Marketplace, or add the server URL in *Settings* > *Tools and MCP*. +* *VS Code*: Add the server via the Extensions view or your workspace `mcp.json`, with GitHub Copilot (or a similar chat agent extension) installed. + +The prompting workflow in the following steps is identical once either editor is connected. + +++++ + +++++ diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-02.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-02.adoc new file mode 100644 index 000000000..2f38b9ab3 --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-02.adoc @@ -0,0 +1,106 @@ += Step 2: Embed the Liveboard view +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 2: Embed the Liveboard view +:page-pageid: spottercode-embed__step-02 +:page-description: Prompt SpotterCode to generate the React code for embedding a ThoughtSpot Liveboard. + +With `init()` in place, the first goal is to render a host's listing-performance Liveboard inside the SpotStay portal. Write a specific prompt rather than a vague one: + +[source,text] +---- +"I want to embed a ThoughtSpot Liveboard showing listing performance in my React app. +Use the available tools to get this information and generate the embed code." +---- + +[div videoContainer] +-- +video::./images/mktng-tutorials/lb-embed-prompt.mp4[width=100%,options="autoplay,loop"] +-- + +What SpotterCode needs to complete the task: + +* The ThoughtSpot host URL (already set in `init()`) +* The Liveboard GUID (retrieved from the Developer Playground) +* Confirmation of the authentication type you're already using + +[source,javascript] +---- +import { LiveboardEmbed, useEmbedRef } from '@thoughtspot/visual-embed-sdk/react'; + +const HostDashboard = () => { + const embedRef = useEmbedRef(); + + return ( + + ); +}; + +export default HostDashboard; +---- + +[#make-it-host-specific] +== Make it host-specific + +A shared Liveboard shows every listing on the platform. You need each host to see only their own. Prompt for a runtime filter scoped to the logged-in host: + +[source,text] +---- +"Show a code example for embedding a Liveboard with a runtime filter where host_id equals +the logged-in user's ID, using the Visual Embed SDK for React." +---- + +[source,javascript] +---- +import { + LiveboardEmbed, + useEmbedRef, + HostEvent, + RuntimeFilterOp, +} from '@thoughtspot/visual-embed-sdk/react'; + +const HostDashboard = ({ hostId }) => { + const embedRef = useEmbedRef(); + + const onLiveboardRendered = () => { + embedRef.current.trigger(HostEvent.UpdateRuntimeFilters, [ + { + columnName: 'host_id', + operator: RuntimeFilterOp.EQ, + values: [hostId], + }, + ]); + }; + + return ( + + ); +}; + +export default HostDashboard; +---- + +[NOTE] +==== +Your prompt has to carry business context ("the logged-in user's ID") that isn't in any documentation. SpotterCode generates the mechanism; you supply the SpotStay-specific meaning. +==== + +[div videoContainer] +-- +video::./images/mktng-tutorials/analytics.mp4[width=100%,options="autoplay,loop"] +-- + +++++ + +++++ diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-03.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-03.adoc new file mode 100644 index 000000000..675f33e99 --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-03.adoc @@ -0,0 +1,109 @@ += Step 3: Embed Spotter: extend it with conversational analytics +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 3: Embed Spotter: Extend it with conversational analytics +:page-pageid: spottercode-embed__step-03 +:page-description: Prompt SpotterCode to embed Spotter beneath the Liveboard, hide actions for external users, and find the Model ID. + +The Liveboard is in place. Now for the second use case: hosts can ask follow-up questions in their own words instead of hunting through Liveboard filters. + +[source,text] +---- +"I want to embed ThoughtSpot Spotter below the Liveboard so hosts can ask follow-up +questions about their listing data. Use the available tools to get the information +and generate the code." +---- + +[div videoContainer] +-- +video::./images/mktng-tutorials/add-spotter.mp4[width=100%,options="autoplay,loop"] +-- + +[source,javascript] +---- +import { SpotterEmbed } from '@thoughtspot/visual-embed-sdk/react'; + +const HostInsights = ({ hostModelId }) => { + return ( + + ); +}; + +export default HostInsights; +---- + +Drop `HostInsights` directly beneath `HostDashboard` on the same portal page; no separate authentication flow is required. + +[#lock-down-for-external-users] +=== Step 1: Lock it down for external users + +Hosts aren't internal ThoughtSpot users. You don't want them downloading raw data or seeing ThoughtSpot-native sharing options that don't make sense in the SpotStay portal. + +[source,text] +---- +"How do I hide the Download and Share actions on an embedded Liveboard, and disable +SpotIQ analysis with a custom message, using the Visual Embed SDK?" +---- + +[source,javascript] +---- + +---- + +[#wire-to-real-data] +=== Step 2: Wire it to real data + +Everything so far used a placeholder Model ID. You still need the actual Model ID for SpotStay's listing data, and you do not need to dig through the ThoughtSpot UI by hand. +If SpotterCode is not authenticated to your cluster, ask it to generate the REST API +request and run the request yourself: + +[source,text] +---- +"Generate a REST API request to find the Model ID for a data object named +'SpotStay Listings', using ThoughtSpot REST API v2." +---- + +If SpotterCode is authenticated to your cluster, ask it to run the lookup directly: + +[source,text] +---- +"Find the Model ID for a data object named 'SpotStay Listings' on my cluster." +---- + +SpotterCode connects as you, runs the lookup, and returns the ID — no copying `curl` commands, no switching tabs. The same pattern works for finding Liveboard GUIDs, checking CORS settings, or inspecting any metadata on your cluster. + +If you prefer to run the lookup manually, use the following REST API request: + +[source,bash] +---- +curl -X POST 'https://spotstay.thoughtspot.cloud/api/rest/2.0/metadata/search' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "metadata": [{ "type": "LOGICAL_TABLE", "name_pattern": "SpotStay Listings" }], + "record_size": 5 + }' +---- + + +[div videoContainer] +-- +video::./images/mktng-tutorials/working-spotter-embed.mp4[width=100%,options="autoplay,loop"] +-- +++++ + +++++ diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-04.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-04.adoc new file mode 100644 index 000000000..b7d15a639 --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-04.adoc @@ -0,0 +1,66 @@ += Step 4: Troubleshooting +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 4: Troubleshooting +:page-pageid: spottercode-embed__step-04 +:page-description: Fix outdated SDK versions, vague prompts, and other common SpotterCode issues. + +These scenarios come up on almost every real project. + +[#outdated-sdk-version] +== Scenario: outdated SDK version + +Your project was scaffolded months ago and still points to an older version of the Visual Embed SDK. You ask for a feature that exists only in a newer release, and the generated code does not run. + +*Fix:* Name the version explicitly and ask what changed. + +[source,text] +---- +"Update my Liveboard embed code to the latest Visual Embed SDK version and list any +breaking changes from my current version in the documentation." +---- + +[#vague-prompts] +== Scenario: vague follow-up gets a vague answer + +`"Make the Liveboard better"` will not produce useful code. Keep prompts scoped to one outcome. + +[cols="1,3", options="header"] +|=== +| Approach | Prompt + +| *Avoid* +| `"Can you customize the embed?"` + +| *Use instead* +| `"Fetch the documentation for LiveboardEmbed and summarize the available customization options."` + +Then pick one option and ask for it by name. +|=== + +[#troubleshooting-checklist] +== General troubleshooting checklist + +[cols="2,3", options="header"] +|=== +| Symptom | Action + +| No response or page not loading +| Confirm SpotterCode is enabled for this project and the MCP server is reachable. + +| Slow responses +| Break the request into smaller, sequential prompts instead of one large request. + +| The response looks wrong +| Check which tool the agent called. Refine the prompt for specificity before assuming the tool is broken. + +| The agent infers SpotStay business logic incorrectly +| You must specify anything that is not in the documentation or the SDK. Do not ask SpotterCode to infer SpotStay-specific business logic. +|=== + +++++ + +++++ diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-05.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-05.adoc new file mode 100644 index 000000000..fb4c6bb19 --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-05.adoc @@ -0,0 +1,39 @@ += Step 5: Lock it down for external users +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 5: Lock it down for external users +:page-pageid: spottercode-embed__step-05 +:page-description: Prompt SpotterCode to hide Download and Share actions and disable SpotIQ analysis for external, non-ThoughtSpot users. + +Hosts aren't internal ThoughtSpot users. +You don't want them downloading raw data or seeing ThoughtSpot-native sharing options that don't make sense in the SpotStay portal. + +[source, text] +---- +How do I hide the Download and Share actions on an embedded Liveboard, +and disable SpotIQ analysis with a custom message, using the Visual Embed SDK? +---- + +SpotterCode returns: + +[source,javascript] +---- +import { Action } from '@thoughtspot/visual-embed-sdk'; + + +---- + +++++ + +++++ diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-06.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-06.adoc new file mode 100644 index 000000000..e95f57857 --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-06.adoc @@ -0,0 +1,39 @@ += Step 6: Wire it to real data +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 6: Wire it to real data +:page-pageid: spottercode-embed__step-06 +:page-description: Use SpotterCode to generate a REST API request that looks up the real worksheet ID for your data. + +Everything so far used a placeholder worksheet ID. +You still need the actual worksheet ID for SpotStay's listing data — no need to dig through the ThoughtSpot UI by hand. + +[source,text] +---- +Generate a REST API request to find the worksheet ID for a data object +named 'SpotStay Listings', using ThoughtSpot REST API v2. +---- + +SpotterCode returns: + +[source,shell] +---- +curl -X POST 'https://your-thoughtspot-host/api/rest/2.0/metadata/search' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "metadata": [{ "type": "LOGICAL_TABLE", "name_pattern": "SpotStay Listings" }], + "record_size": 5 + }' +---- + +Replace `your-thoughtspot-host` with your instance hostname and `` with a valid bearer token. +The response includes the model GUID. Copy it into your `SpotterEmbed` and `LiveboardEmbed` components. + +++++ + +++++ diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-07.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-07.adoc new file mode 100644 index 000000000..a6c53e6d9 --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-step-07.adoc @@ -0,0 +1,44 @@ += Step 7: When it doesn't just work +:toc!: +:linkattrs: +:sectanchors: +:page-title: Step 7: When it doesn't just work +:page-pageid: spottercode-embed__step-07 +:page-description: Troubleshooting patterns for outdated SDK versions, vague prompts, and other common SpotterCode issues. + +Worth building into the guide on purpose, because it happens on every real project. + +== Scenario: outdated SDK version + +Your project was scaffolded months ago and still points at an older Visual Embed SDK version. +You ask for a feature that only exists in a newer release, and the generated code doesn't run. + +Fix: name the version explicitly and ask what changed. + +[source] +---- +Update my Liveboard embed code to the latest Visual Embed SDK version +and list any breaking changes from my current version in the documentation. +---- + +== Scenario: vague follow-up gets a vague answer + +"Make the dashboard better" will not produce useful code. +Keep prompts scoped to one outcome: + +* Not: "Can you customize the embed?" +* Instead: "Fetch the documentation for `LiveboardEmbed` and summarize the available customization options." Then pick one and ask for it by name. + +== General troubleshooting checklist + +* No response or not loading → confirm SpotterCode is enabled for this project and the MCP server is reachable. +* Slow responses → break the ask into smaller, sequential prompts instead of one large request. +* Response looks wrong → check which tool the agent actually called. Refine the prompt for specificity before assuming the tool is broken. +* Don't ask it to infer SpotStay's business logic. Anything not in the docs or the SDK, you still have to specify yourself. + +++++ + +++++ diff --git a/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-summary.adoc b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-summary.adoc new file mode 100644 index 000000000..a67c81097 --- /dev/null +++ b/modules/tutorials/pages/spottercode-embed/build-embed-app-with-spottercode-summary.adoc @@ -0,0 +1,32 @@ += Summary +:toc!: +:linkattrs: +:sectanchors: +:page-title: Summary +:page-pageid: spottercode-embed__summary +:page-description: Wrap-up and next steps after building a host-scoped Liveboard and Spotter embed with SpotterCode. + +You shipped two use cases: a host-scoped Liveboard and a conversational Spotter panel, both locked down and wired to real data. The pattern underneath did not change between them. It also does not change for whatever you build next, whether that is a Liveboard for property hosts, a Spotter embed in a mobile app, or a REST job that syncs booking data on a schedule. + +Every task follows the same loop: + +. Write one specific, terminology-precise prompt that names the exact ThoughtSpot object and the SDK you're using. +. Let SpotterCode pull the real, current documentation reference and generate code shaped to your project. +. Review it. You are checking the code, not accepting it as-is. +. If something is off, narrow the prompt before assuming the tool cannot do it. + +== Next steps + +* xref:spottercode.adoc[SpotterCode reference] +* xref:embed-spotter.adoc[Embed Spotter]: full reference +* xref:embed-pinboard.adoc[Embed a Liveboard]: full reference +* xref:embed-authentication.adoc[Authentication options] +* xref:build-data-driven-app-with-tse-intro.adoc[Build a data-driven app with ThoughtSpot Embedded] +* link:https://github.com/thoughtspot/developer-examples[Developer examples on GitHub, window=_blank] + +++++ + +++++ diff --git a/modules/tutorials/pages/style-customization/style-customization_tutorial-intro.adoc b/modules/tutorials/pages/style-customization/style-customization_tutorial-intro.adoc new file mode 100644 index 000000000..5b20011d4 --- /dev/null +++ b/modules/tutorials/pages/style-customization/style-customization_tutorial-intro.adoc @@ -0,0 +1,32 @@ += Style customization tutorial +:toc!: + +:page-title: Visual Embed SDK Style customization tutorial +:page-pageid: style-customization__intro +:page-description: This is a self-guided course on style customization of the Visual Embed SDK components + +One of the main advantages of ThoughtSpot Embedded is the control over the styling and UI elements provided by the Visual Embed SDK. + +This tutorial provides a walkthrough of customizable elements of ThoughtSpot UI and essential steps to style your embedded ThoughtSpot content. + +ThoughtSpot allows customizing the following aspects of its UI: + +1. Large component elements +2. CSS styles +3. Text strings and icon replacement +4. Menu items + +== Steps in this tutorial + +. xref:style-customization_tutorial-step-01.adoc[Getting started] +. xref:style-customization_tutorial-step-02.adoc[Large component elements] +. xref:style-customization_tutorial-step-03.adoc[CSS styles] +. xref:style-customization_tutorial-step-04.adoc[Text strings and icon replacement] +. xref:style-customization_tutorial-step-05.adoc[Menu items] +. xref:style-customization_tutorial-step-06.adoc[Conclusion] + +++++ + +++++ diff --git a/modules/tutorials/pages/style-customization/style-customization_tutorial-step-01.adoc b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-01.adoc new file mode 100644 index 000000000..b4de30530 --- /dev/null +++ b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-01.adoc @@ -0,0 +1,100 @@ += Step 1: Getting started +:toc!: + +:page-title: Step 1: Getting started +:page-pageid: style-customization__step-01 +:page-description: Use the Visual Embed SDK Playground to demonstrate customizations in a live environment. + +This tutorial uses the Visual Embed SDK Playground built into every ThoughtSpot instance to demonstrate various customizations in a live environment. + +We suggest copying the code into a text editor or IDE such as Visual Studio Code to make it easier to work on the examples, and then copy the values back into the code editor within the Playground. + +To get started: + +. Select the `Liveboard` component to embed in the Playground: ++ +[.widthAuto] +[.bordered] +image:images/tutorials/style-customization/choose-liveboard-embed.png[Choose LiveboardEmbed component] +. Select the *(Sample) Sales Performance* Liveboard, which should be available on all instances, including the Free Trial setup. ++ +[.widthAuto] +[.bordered] +image:images/tutorials/style-customization/choose-liveboard.png[Choose Liveboard] + +. If you do not have "(Sample) Sales Performance" available, use any Liveboard that you have access to. ++ +You should see the following code in the code panel of the Playground page. + +. Copy at any point down to `.render();`: ++ +[source,javascript] +---- +// Import ThoughtSpot SDK +import { + init, + LiveboardEmbed, + Action, + RuntimeFilterOp, + EmbedEvent, + AuthType, + HostEvent +} from "@thoughtspot/visual-embed-sdk"; +import "./styles.css"; +import { + getTokenService +} from "./tokenService"; + +// Use prefetch to load static resources early and optimize response time. +// Call init early to complete authentication during app load for better performance. +init({ + thoughtSpotHost: + /*param-start-hosturl*/"https://embed-1-do-not-delete.thoughtspotstaging.cloud" /*param-end-hosturl*/, + /*param-start-styleCustomization*//*param-end-styleCustomization*/ + authType: AuthType.TrustedAuthTokenCookieless, + getAuthToken: getTokenService, + disableTokenVerification: true, +}); + +// Instantiate class for embedding a Liveboard +const embed = new LiveboardEmbed("#your-own-div", { + frameParams: {}, + /*param-start-liveboardId*/liveboardId: "d084c256-e284-4fc4-b80c-111cb606449a" /*param-end-liveboardId*/ + /*param-start-activeTabId*//*param-end-activeTabId*/ + /*param-start-liveboardFullHeight*//*param-end-liveboardFullHeight*/ + /*param-start-customizeLiveboardHeader*//*param-end-customizeLiveboardHeader*/ + /*param-start-hideLiveboardHeader*//*param-end-hideLiveboardHeader*/ + /*param-start-runtimeFiltersAndParameters*//*param-end-runtimeFiltersAndParameters*/ + /*param-start-modifyActions*//*param-end-modifyActions*/ +}); + +hideNoDataImage(); +showErrorBanner('none'); + +embed + // Register event listeners + .on(EmbedEvent.Init, showLoader) + .on(EmbedEvent.Load, hideLoader) + /*param-start-customActionHandle*//*param-end-customActionHandle*/ + .on(EmbedEvent.Error, (error) => { + if (error?.data?.errorType === 'FULLSCREEN') { + showErrorBanner('none'); + } else + if (typeof(error.error) === 'string') { + showErrorBanner('flex', error.error); + } else { + showErrorBanner('flex'); + } + console.log('Error ', error); + hideLoader(); + }) + // Render Liveboard + .render(); +---- + +++++ + +++++ diff --git a/modules/tutorials/pages/style-customization/style-customization_tutorial-step-02.adoc b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-02.adoc new file mode 100644 index 000000000..74b307c13 --- /dev/null +++ b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-02.adoc @@ -0,0 +1,85 @@ += Step 2: Large component elements +:toc!: + +:page-title: Step 2: Large component elements +:page-pageid: style-customization__step-02 +:page-description: Customize large component elements such as the Liveboard header and tab panel from the Playground. + +The checkboxes above the code panel provide quick access to the code to customize embedded elements. + +. Click *Customize Liveboard header* checkbox. You'll see the following code block in the code editor: ++ +[source,javascript] +---- + /* Show or hide the liveboard title and description */ + showLiveboardTitle: true, + showLiveboardDescription: true, + // isLiveboardHeaderSticky: false, /* Removes the header stickiness when scrolling if set to false */ + // hideLiveboardHeader: true, /* Hides the entire liveboard header */ + + /* Customize display of tabs in the liveboard header */ + // hideTabPanel: true, /* Removes the tab panel */ + // visibleTabs:['TabId'], /* Remove all tabs if empty array*/ + // hiddenTabs:['TabId'], + /* Use either visibleTabs or hiddenTabs */ +---- ++ +If the *Customize Liveboard header* checkbox is unavailable on your instance of ThoughtSpot, check *Full Height* then copy the code above over the `fullHeight: true` section that is added to the Code Editor. + ++ +[NOTE] +==== +The code includes examples of several settings that are commented out using the `//` line comment. Removing `//` enables these settings and allows you to include them in your code. +==== + +. Click *Run*. The `LiveboardEmbed` component loads with the new options. +. Remove `//` before the `hideLiveboardHeader: true` property to enable this setting. +. Click *Run* again to see the updated results. + +== Available options for component element customization +There are many more configurations available for each component than those available in the Playground. To see all available options, see link:https://developers.thoughtspot.com/docs/VisualEmbedSdk#_interfaces[Embed Classes^] and link:https://developers.thoughtspot.com/docs/VisualEmbedSdk#_interfaces[Embed Interfaces^]. + + +[.bordered] +image:images/tutorials/style-customization/embed-components-reference.png[Components reference] + +To use a setting that is not automatically generated by the Playground, go to its link:https://developers.thoughtspot.com/docs/Interface_LiveboardViewConfig[definition within the reference page^] and look at the example code. + +To override user's locale settings: + +. Click link:https://developers.thoughtspot.com/docs/Interface_LiveboardViewConfig#_locale[locale^]. +. Copy the property into the code in the Playground and specify a valid locale attribute. For example, `fr` or `it`: ++ +[source,javascript] +---- + /* We added this one ourselves */ + locale: 'fr', + /* Show or hide the Liveboard title and description */ + showLiveboardTitle: true, + showLiveboardDescription: true, + // isLiveboardHeaderSticky: false, /* Removes the header stickiness when scrolling if set to false */ + // hideLiveboardHeader: true, /* Hides the entire liveboard header */ + + /* Customize display of tabs in the liveboard header */ + // hideTabPanel: true, /* Removes the tab panel */ + // visibleTabs:['TabId'], /* Remove all tabs if empty array*/ + // hiddenTabs:['TabId'], + /* Use either visibleTabs or hiddenTabs */ +---- + +. Click *Run* and try some of the items in the menu system when the Liveboard loads. ++ +[.bordered] +image:images/tutorials/style-customization/locale-override.png[Locale override property] + +== Hiding elements without configuration options +If there are elements of the page that you can't find an option to hide in the embedded interface configuration options, you may need to xref:style-customization_tutorial-step-03.adoc#_hide_elements[use the CSS customization framework to hide a given element] directly. + +If your implementation requires hiding certain elements, let ThoughtSpot know the element for which you need a configuration option by submitting an idea to the link:https://community.thoughtspot.com/s/ideas[ThoughtSpot Community^]. + +++++ + +++++ diff --git a/modules/tutorials/pages/style-customization/style-customization_tutorial-step-03.adoc b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-03.adoc new file mode 100644 index 000000000..751027c1a --- /dev/null +++ b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-03.adoc @@ -0,0 +1,151 @@ += Step 3: CSS styles +:toc!: + +:page-title: Step 3: CSS styles +:page-pageid: style-customization__step-03 +:page-description: Use the customizations framework to apply CSS variables and selectors to embedded ThoughtSpot content. + +The Visual Embed SDK has a `customizations` framework for adding CSS and other overrides. + +In the Playground, select the *Apply custom styles* checkbox. The code panel shows a large block of code with various options for CSS customization: + +[NOTE] +==== +The `customizations` code goes in the `init()` function, whereas the other customizations are applied when the embedded component initializes. +==== + +[source,javascript] +---- +customizations: { + style: { + customCSSUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/css-variables.css", // location of your style sheet + + // To apply overrides for your style sheet in this init, provide variable values below, eg + customCSS: { + variables: { + "--ts-var-button--secondary-background": "#F0EBFF", + "--ts-var-button--secondary--hover-background": "#E3D9FC", + "--ts-var-root-background": "#F7F5FF", + }, + }, + }, +}, +---- + +== Variables and selectors +ThoughtSpot provides many pre-defined xref:customize-css-styles.adoc[CSS variables] to control the styling of the embedded component UI and its elements. + +The style definitions can all be declared directly within the `variables` block of the `customCSS` code. + +The customization framework also allows using any CSS selector to target specific elements with changes that do not have a defined variable. Selectors can be declared within the `rules_UNSTABLE` block inside `customCSS`. However, selectors may change with new releases as elements of ThoughtSpot are updated. + +Let's add a selector to the code in our Playground. First, comment out the `customCSSUrl` line, then add the `rules_UNSTABLE` block below `variables`: + +[source,javascript] +---- +customizations: { + style: { + // customCSSUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/css-variables.css", // location of your style sheet + + // To apply overrides for your style sheet in this init, provide variable values below, eg + customCSS: { + variables: { + "--ts-var-button--secondary-background": "#F0EBFF", + "--ts-var-button--secondary--hover-background": "#E3D9FC", + "--ts-var-root-background": "#F7F5FF", + }, + rules_UNSTABLE: { + + } + }, + }, +}, +---- + +Variables declare a __single__ property, therefore are defined as `"{var-name}" : "{value}"`, whereas selectors allow you to assign several properties to the selected elements. + +[NOTE] +==== +Selectors apply properties to elements with many layers of styling. Always add `!important` after each property to ensure the browser overrides whatever other style rules may be applied for the same property. +==== + +One use case of the `rules_UNSTABLE` section is `xref:css-customization.adoc#font-declarations[@font-face]` declarations, which have many properties for one selector. + +We'll switch the main font to link:https://fonts.google.com/specimen/Poppins[Poppins^], available from Google Fonts: + +. Add the `--ts-var-root-font-family` variable to declare the new font. + +Note that you'll need to use this exact name value in `@font-face` declarations. +. Add a selector block within the `rules_UNSTABLE` block. +. Include xref:css-customization.adoc#_font_declarations[font declarations]. ++ +[source,javascript] +---- +// ... + customCSS: { + variables: { + "--ts-var-button--secondary-background": "#F0EBFF", + "--ts-var-button--secondary--hover-background": "#E3D9FC", + "--ts-var-root-background": "#F7F5FF", + "--ts-var-root-font-family": "Poppins" + }, + rules_UNSTABLE: { + '/* ff-400 */ @font-face': { + 'font-family': "Poppins", + 'font-style': 'normal', + 'font-weight': '400', + 'font-display': 'swap', + 'src': "url(https://fonts.gstatic.com/s/poppins/v21/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2) format('woff2')" + } + } + }, +---- ++ +Notice the format shows the selector as the __key__, then an object block containing individual key-value pairs for the properties. Because the selector is an object key, but all `@font-face` declarations start the same way, we add a unique CSS comment at the beginning to allow for multiple `@font-face` declarations. + +. Click *Run*. +. Notice the Liveboard reload with the `Poppins` font for most of the text. + +== CSS files +You can collect a set of variables and selectors into a CSS file, rather than declaring them in the JavaScript code block. +CSS files can be included from any domain, but they must be added to the *CSP style-src domains* and *CSP font-src* domains on the *Develop* > *Customizations* -> *Security settings* page. + +Both `\https://cdn.jsdelivr.net` and `\https://fonts.gstatic.com` sites are automatically added to ThoughtSpot's CSP allowlist. + +In your CSS file, the global variables must be declared in the `:root { }` block, while `@font-face` declarations of a named font can be placed anywhere: + +[,css] +---- +:root { + --ts-var-button--primary-background: #2359B6; + --ts-var-button--primary--hover-background: blue; + --ts-var-button--primary--font-family: Poppins,Helvetica,Arial,sans-serif;; +} + +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/poppins/v21/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2) format('woff2'); +} + +.bk-filter-option { + display: none!important; +} +---- + +== Hide elements +As seen in the CSS file example above, one of the use cases for selectors is to hide embed component elements that do not have a configuration option. + +`display: none!important` is the most typical property to accomplish this, but you may choose any CSS rule that causes the desired effect. + +Make sure that the selector you use is specific and does not affect other elements that you don't intend to hide. + +If you have been hiding certain elements via CSS selectors, link:https://community.thoughtspot.com/s/ideas[contact ThoughtSpot^] to request configuration options for such elements, so that the overall configurations can be expanded over time. Similarly, provide feedback on properties that variables are unavailable by submitting an idea to the link:https://community.thoughtspot.com/s/ideas[ThoughtSpot Community^]. + +++++ + +++++ diff --git a/modules/tutorials/pages/style-customization/style-customization_tutorial-step-04.adoc b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-04.adoc new file mode 100644 index 000000000..847d09907 --- /dev/null +++ b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-04.adoc @@ -0,0 +1,58 @@ += Step 4: Text strings and icon replacement +:toc!: + +:page-title: Step 4: Text strings and icon replacement +:page-pageid: style-customization__step-04 +:page-description: Replace text strings and icon sprites in embedded ThoughtSpot content using the customizations framework. + +The `customizations` object allows replacing replace specific xref:customize-text-strings.adoc[text strings] and xref:customize-icons.adoc[icons]. + +== Replace text strings + +Let's add the `content` property above the `style` property in the `customizations` code and then add the `strings` property block within this code. Now we can declare the exact text elements to replace within ThoughtSpot: + +[source,javascript] +---- +// ... +customizations: { + content: { + strings: { + "Liveboard": "Dashboard", + "SpotIQ": "Insights", + "Spotter": "AI Agent" + } + }, + style: { + ... + }, +}, +---- + +== Replace icon sprites + +Icon sprite replacement requires creating a file of SVG icon definitions, the format for which is available in the xref:customize-icons.adoc[icon sprite customization documentation]. After you have the SVG file saved and accessible to ThoughtSpot, add the `iconSpriteUrl` property in the `customizations` block: + +[source,javascript] +---- +// ... +customizations: { + iconSpriteUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/icon-override1.svg", + content: { + strings: { + "Liveboard": "Dashboard", + "SpotIQ": "Insights", + "Spotter": "AI Agent" + } + }, + style: { + ... + }, +}, +---- + +++++ + +++++ diff --git a/modules/tutorials/pages/style-customization/style-customization_tutorial-step-05.adoc b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-05.adoc new file mode 100644 index 000000000..2a29e6605 --- /dev/null +++ b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-05.adoc @@ -0,0 +1,117 @@ += Step 5: Menu items +:toc!: + +:page-title: Step 5: Menu items +:page-pageid: style-customization__step-05 +:page-description: Hide, disable, and trigger ThoughtSpot menu items and custom actions from the embedded application. + +ThoughtSpot menus are accessible in the top right corner with the ellipsis icon (`...`) or via a right-click on a chart axis or data point. The `...` menu is referred to as the *More* options menu. + +On Liveboards, a top menu for the Liveboard and a separate menu for each visualization is available. The menu from right-clicking a data point is referred to as the *Context menu*. + +== Hiding or disabling items +Individual menu items are controlled by their capabilities and are referred to as *Actions*. The Visual Embed SDK reference guide for link:https://developers.thoughtspot.com/docs/Enumeration_Action[Actions^] contains a complete list of named capabilities. + +In the Playground, select the checkbox for `Modify available actions`. You'll see the following code in the code editor: + +[source,javascript] +---- + disabledActions: [], + disabledActionReason: "Reason for disabling", + // visibleActions: [], /* Removes all actions if empty array */ + hiddenActions: [], + /* Use either visibleActions or hiddenActions */ +---- + +If you want to show only a small set of selected menu items, use `visibleActions` (an allowlist) and comment out `hiddenActions` (a deny list). + +Let's show only the `DownloadAsPdf` action: + +[source,javascript] +---- + disabledActions: [], + disabledActionReason: "Reason for disabling", + visibleActions: [Action.DownloadAsPdf], /* Removes all actions if empty array */ + //hiddenActions: [], + /* Use either visibleActions or hiddenActions */ +---- + +When you click *Run*, the Liveboard reloads with only a single menu item in the *More* options menu as specified in the `visibleActions` array. + +[NOTE] +==== +The above example also hides the right-click context menu items, including the *Drill down* action (`Action.DrillDown`). +==== + +The `disabledActions` array keeps the item in the menu but grays it out, and shows `disabledActionReason` when hovering over the disabled action. + +== Triggering hidden menu items with HostEvents +ThoughtSpot Visual Embed SDK defines two types of events: + +* link:https://developers.thoughtspot.com/docs/Enumeration_EmbedEvent[EmbedEvents^] - to listen to actions within ThoughtSpot components +* link:https://developers.thoughtspot.com/docs/Enumeration_HostEvent[HostEvents^] - to send messages to the ThoughtSpot components from the embedding application. + +If a menu item has been hidden, you can still send in a *HostEvent* to cause the same behavior. + +In the Playground, select the *Use Host Event* checkbox. You'll see the following code block in the code editor: + +[source,javascript] +---- + document.getElementById('tryBtn').addEventListener('click', e => { + // Trigger events can be added here to bind to try button click! + // eg use the Reload Event so that clicking on "Try event" button reloads the embed: + embed.trigger(HostEvent.Reload); + }); +---- + +The above code block adds a click event to the *Try Event* button above the preview panel in the Playground. Clicking *Try Event* triggers the `HostEvent`. + +Let's replace the default `Reload` event with `DownloadAsPdf`: + +[source,javascript] +---- + document.getElementById('tryBtn').addEventListener('click', e => { + // Trigger events can be added here to bind to try button click! + // eg use the Reload Event so that clicking on "Try event" button reloads the embed: + embed.trigger(HostEvent.DownloadAsPdf); + }); +---- + +Testing this requires the following steps: + +1. Click *Run* to reload the embedded component. +2. Click *Try Event*. + +You should see the PDF export modal dialog button pop up within the embedded component area. + +If you do not want the modal dialog to appear, you could instead use the ThoughtSpot REST API to accomplish the task either within the browser or in a back-end process. This allows for choosing vastly different behaviors than those allowed by the ThoughtSpot modal dialogs. + +== Adding new menu items with custom actions +ThoughtSpot allows you to add new items called *xref:custom-actions.adoc[custom actions]* to the menu system, either to the *More* options menu on a given visualization of a Liveboard or the context menu that appears when a single point is right-clicked. + +The *Callback* custom actions require a three-part setup: + +1. Define the custom action within ThoughtSpot, with a particular *id*. +2. Assign the custom action to the visualization. +3. Add the `EmbedEvent.CustomAction` listener within the Visual Embed SDK code. + +To try it out, select the `Handle custom actions` checkbox. You'll see the following code block in the code editor: + +[source,javascript] +---- + .on(EmbedEvent.CustomAction, payload => { + const customActionId = 'insert Custom Action ID here'; + if (payload.id === customActionId || payload.data.id === customActionId) { + console.log('Custom Action event:', payload.data); + } + }) +---- + +Some link:https://developers.thoughtspot.com/docs/Enumeration_EmbedEvent[EmbedEvents^] such as `VizPointClick` fire off without involving the menu system and function similarly to context menu custom action. + +++++ + +++++ diff --git a/modules/tutorials/pages/style-customization/style-customization_tutorial-step-06.adoc b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-06.adoc new file mode 100644 index 000000000..6e4e41fea --- /dev/null +++ b/modules/tutorials/pages/style-customization/style-customization_tutorial-step-06.adoc @@ -0,0 +1,17 @@ += Step 6: Conclusion +:toc!: + +:page-title: Step 6: Conclusion +:page-pageid: style-customization__step-06 +:page-description: Wrap up the style customization tutorial and next steps for integrating into your application. + +This tutorial has covered how to customize the UI and UX of ThoughtSpot's embedded components, but only within the Visual Embed SDK Playground. + +The next steps are to move this code into your application page and configure the CSP and CORS settings for your application's test environments, so you begin integrating ThoughtSpot directly into your application. + +++++ + +++++ diff --git a/modules/tutorials/pages/style-customization/style-customization_tutorial.adoc b/modules/tutorials/pages/style-customization/style-customization_tutorial.adoc deleted file mode 100644 index c3907250e..000000000 --- a/modules/tutorials/pages/style-customization/style-customization_tutorial.adoc +++ /dev/null @@ -1,479 +0,0 @@ -= Style customization tutorial -:toc: true -:toclevels: 1 - -:page-title: Visual Embed SDK Style customization tutorial -:page-pageid: style-customization__tutorial -:page-description: This is a self-guided course on style customization of the Visual Embed SDK components - - -One of the main advantages of ThoughtSpot Embedded is the control over the styling and UI elements provided by the Visual Embed SDK. - -This tutorial provides a walkthrough of customizable elements of ThoughtSpot UI and essential steps to style your embedded ThoughtSpot content. - -ThoughtSpot allows customizing the following aspects of its UI: - -1. Large component elements -2. CSS styles -3. Text strings and icon replacement -4. Menu items - - -== 01 - Getting started - -This tutorial uses the Visual Embed SDK Playground built into every ThoughtSpot instance to demonstrate various customizations in a live environment. - -We suggest copying the code into a text editor or IDE such as Visual Studio Code to make it easier to work on the examples, and then copy the values back into the code editor within the Playground. - -To get started: - -. Select the `Liveboard` component to embed in the Playground: -+ -[.widthAuto] -[.bordered] -image:images/tutorials/style-customization/choose-liveboard-embed.png[Choose LiveboardEmbed component] -. Select the *(Sample) Sales Performance* Liveboard, which should be available on all instances, including the Free Trial setup. -+ -[.widthAuto] -[.bordered] -image:images/tutorials/style-customization/choose-liveboard.png[Choose Liveboard] - -. If you do not have "(Sample) Sales Performance" available, use any Liveboard that you have access to. -+ -You should see the following code in the code panel of the Playground page. - -. Copy at any point down to `.render();`: -+ -[source,javascript] ----- -// Import ThoughtSpot SDK -import { - init, - LiveboardEmbed, - Action, - RuntimeFilterOp, - EmbedEvent, - AuthType, - HostEvent -} from "@thoughtspot/visual-embed-sdk"; -import "./styles.css"; -import { - getTokenService -} from "./tokenService"; - -// Use prefetch to load static resources early and optimize response time. -// Call init early to complete authentication during app load for better performance. -init({ - thoughtSpotHost: - /*param-start-hosturl*/"https://embed-1-do-not-delete.thoughtspotstaging.cloud" /*param-end-hosturl*/, - /*param-start-styleCustomization*//*param-end-styleCustomization*/ - authType: AuthType.TrustedAuthTokenCookieless, - getAuthToken: getTokenService, - disableTokenVerification: true, -}); - -// Instantiate class for embedding a Liveboard -const embed = new LiveboardEmbed("#your-own-div", { - frameParams: {}, - /*param-start-liveboardId*/liveboardId: "d084c256-e284-4fc4-b80c-111cb606449a" /*param-end-liveboardId*/ - /*param-start-activeTabId*//*param-end-activeTabId*/ - /*param-start-liveboardFullHeight*//*param-end-liveboardFullHeight*/ - /*param-start-customizeLiveboardHeader*//*param-end-customizeLiveboardHeader*/ - /*param-start-hideLiveboardHeader*//*param-end-hideLiveboardHeader*/ - /*param-start-runtimeFiltersAndParameters*//*param-end-runtimeFiltersAndParameters*/ - /*param-start-modifyActions*//*param-end-modifyActions*/ -}); - -hideNoDataImage(); -showErrorBanner('none'); - -embed - // Register event listeners - .on(EmbedEvent.Init, showLoader) - .on(EmbedEvent.Load, hideLoader) - /*param-start-customActionHandle*//*param-end-customActionHandle*/ - .on(EmbedEvent.Error, (error) => { - if (error?.data?.errorType === 'FULLSCREEN') { - showErrorBanner('none'); - } else - if (typeof(error.error) === 'string') { - showErrorBanner('flex', error.error); - } else { - showErrorBanner('flex'); - } - console.log('Error ', error); - hideLoader(); - }) - // Render Liveboard - .render(); ----- - -== 02 - Large component elements - -The checkboxes above the code panel provide quick access to the code to customize embedded elements. - -. Click *Customize Liveboard header* checkbox. You'll see the following code block in the code editor: -+ -[source,javascript] ----- - /* Show or hide the liveboard title and description */ - showLiveboardTitle: true, - showLiveboardDescription: true, - // isLiveboardHeaderSticky: false, /* Removes the header stickiness when scrolling if set to false */ - // hideLiveboardHeader: true, /* Hides the entire liveboard header */ - - /* Customize display of tabs in the liveboard header */ - // hideTabPanel: true, /* Removes the tab panel */ - // visibleTabs:['TabId'], /* Remove all tabs if empty array*/ - // hiddenTabs:['TabId'], - /* Use either visibleTabs or hiddenTabs */ ----- -+ -If the *Customize Liveboard header* checkbox is unavailable on your instance of ThoughtSpot, check *Full Height* then copy the code above over the `fullHeight: true` section that is added to the Code Editor. - -+ -[NOTE] -==== -The code includes examples of several settings that are commented out using the `//` line comment. Removing `//` enables these settings and allows you to include them in your code. -==== - -. Click *Run*. The `LiveboardEmbed` component loads with the new options. -. Remove `//` before the `hideLiveboardHeader: true` property to enable this setting. -. Click *Run* again to see the updated results. - -=== Available options for component element customization -There are many more configurations available for each component than those available in the Playground. To see all available options, see link:https://developers.thoughtspot.com/docs/VisualEmbedSdk#_interfaces[Embed Classes^] and link:https://developers.thoughtspot.com/docs/VisualEmbedSdk#_interfaces[Embed Interfaces^]. - - -[.bordered] -image:images/tutorials/style-customization/embed-components-reference.png[Components reference] - -To use a setting that is not automatically generated by the Playground, go to its link:https://developers.thoughtspot.com/docs/Interface_LiveboardViewConfig[definition within the reference page^] and look at the example code. - -To override user's locale settings: - -. Click link:https://developers.thoughtspot.com/docs/Interface_LiveboardViewConfig#_locale[locale^]. -. Copy the property into the code in the Playground and specify a valid locale attribute. For example, `fr` or `it`: -+ -[source,javascript] ----- - /* We added this one ourselves */ - locale: 'fr', - /* Show or hide the Liveboard title and description */ - showLiveboardTitle: true, - showLiveboardDescription: true, - // isLiveboardHeaderSticky: false, /* Removes the header stickiness when scrolling if set to false */ - // hideLiveboardHeader: true, /* Hides the entire liveboard header */ - - /* Customize display of tabs in the liveboard header */ - // hideTabPanel: true, /* Removes the tab panel */ - // visibleTabs:['TabId'], /* Remove all tabs if empty array*/ - // hiddenTabs:['TabId'], - /* Use either visibleTabs or hiddenTabs */ ----- - -. Click *Run* and try some of the items in the menu system when the Liveboard loads. -+ -[.bordered] -image:images/tutorials/style-customization/locale-override.png[Locale override property] - -=== Hiding elements without configuration options -If there are elements of the page that you can't find an option to hide in the embedded interface configuration options, you may need to xref:_hide_elements[use the CSS customization framework to hide a given element] directly. - -If your implementation requires hiding certain elements, let ThoughtSpot know the element for which you need a configuration option by submitting an idea to the link:https://community.thoughtspot.com/s/ideas[ThoughtSpot Community^]. - -== 03 - CSS styles -The Visual Embed SDK has a `customizations` framework for adding CSS and other overrides. - -In the Playground, select the *Apply custom styles* checkbox. The code panel shows a large block of code with various options for CSS customization: - -[NOTE] -==== -The `customizations` code goes in the `init()` function, whereas the other customizations are applied when the embedded component initializes. -==== - -[source,javascript] ----- -customizations: { - style: { - customCSSUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/css-variables.css", // location of your style sheet - - // To apply overrides for your style sheet in this init, provide variable values below, eg - customCSS: { - variables: { - "--ts-var-button--secondary-background": "#F0EBFF", - "--ts-var-button--secondary--hover-background": "#E3D9FC", - "--ts-var-root-background": "#F7F5FF", - }, - }, - }, -}, ----- - -=== Variables and selectors -ThoughtSpot provides many pre-defined xref:customize-css-styles.adoc[CSS variables] to control the styling of the embedded component UI and its elements. - -The style definitions can all be declared directly within the `variables` block of the `customCSS` code. - -The customization framework also allows using any CSS selector to target specific elements with changes that do not have a defined variable. Selectors can be declared within the `rules_UNSTABLE` block inside `customCSS`. However, selectors may change with new releases as elements of ThoughtSpot are updated. - -Let's add a selector to the code in our Playground. First, comment out the `customCSSUrl` line, then add the `rules_UNSTABLE` block below `variables`: - -[source,javascript] ----- -customizations: { - style: { - // customCSSUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/css-variables.css", // location of your style sheet - - // To apply overrides for your style sheet in this init, provide variable values below, eg - customCSS: { - variables: { - "--ts-var-button--secondary-background": "#F0EBFF", - "--ts-var-button--secondary--hover-background": "#E3D9FC", - "--ts-var-root-background": "#F7F5FF", - }, - rules_UNSTABLE: { - - } - }, - }, -}, ----- - -Variables declare a __single__ property, therefore are defined as `"{var-name}" : "{value}"`, whereas selectors allow you to assign several properties to the selected elements. - -[NOTE] -==== -Selectors apply properties to elements with many layers of styling. Always add `!important` after each property to ensure the browser overrides whatever other style rules may be applied for the same property. -==== - -One use case of the `rules_UNSTABLE` section is `xref:css-customization.adoc#font-declarations[@font-face]` declarations, which have many properties for one selector. - -We'll switch the main font to link:https://fonts.google.com/specimen/Poppins[Poppins^], available from Google Fonts: - -. Add the `--ts-var-root-font-family` variable to declare the new font. + -Note that you'll need to use this exact name value in `@font-face` declarations. -. Add a selector block within the `rules_UNSTABLE` block. -. Include xref:css-customization.adoc#_font_declarations[font declarations]. -+ -[source,javascript] ----- -// ... - customCSS: { - variables: { - "--ts-var-button--secondary-background": "#F0EBFF", - "--ts-var-button--secondary--hover-background": "#E3D9FC", - "--ts-var-root-background": "#F7F5FF", - "--ts-var-root-font-family": "Poppins" - }, - rules_UNSTABLE: { - '/* ff-400 */ @font-face': { - 'font-family': "Poppins", - 'font-style': 'normal', - 'font-weight': '400', - 'font-display': 'swap', - 'src': "url(https://fonts.gstatic.com/s/poppins/v21/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2) format('woff2')" - } - } - }, ----- -+ -Notice the format shows the selector as the __key__, then an object block containing individual key-value pairs for the properties. Because the selector is an object key, but all `@font-face` declarations start the same way, we add a unique CSS comment at the beginning to allow for multiple `@font-face` declarations. - -. Click *Run*. -. Notice the Liveboard reload with the `Poppins` font for most of the text. - -=== CSS files -You can collect a set of variables and selectors into a CSS file, rather than declaring them in the JavaScript code block. -CSS files can be included from any domain, but they must be added to the *CSP style-src domains* and *CSP font-src* domains on the *Develop* > *Customizations* -> *Security settings* page. - -Both `\https://cdn.jsdelivr.net` and `\https://fonts.gstatic.com` sites are automatically added to ThoughtSpot's CSP allowlist. - -In your CSS file, the global variables must be declared in the `:root { }` block, while `@font-face` declarations of a named font can be placed anywhere: - -[,css] ----- -:root { - --ts-var-button--primary-background: #2359B6; - --ts-var-button--primary--hover-background: blue; - --ts-var-button--primary--font-family: Poppins,Helvetica,Arial,sans-serif;; -} - -@font-face { - font-family: 'Poppins'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url(https://fonts.gstatic.com/s/poppins/v21/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2) format('woff2'); -} - -.bk-filter-option { - display: none!important; -} ----- - -=== Hide elements -As seen in the CSS file example above, one of the use cases for selectors is to hide embed component elements that do not have a configuration option. - -`display: none!important` is the most typical property to accomplish this, but you may choose any CSS rule that causes the desired effect. - -Make sure that the selector you use is specific and does not affect other elements that you don't intend to hide. - -If you have been hiding certain elements via CSS selectors, link:https://community.thoughtspot.com/s/ideas[contact ThoughtSpot^] to request configuration options for such elements, so that the overall configurations can be expanded over time. Similarly, provide feedback on properties that variables are unavailable by submitting an idea to the link:https://community.thoughtspot.com/s/ideas[ThoughtSpot Community^]. - -== 04 - Text strings and icon replacement -The `customizations` object allows replacing replace specific xref:customize-text-strings.adoc[text strings] and xref:customize-icons.adoc[icons]. - -=== Replace text strings - -Let's add the `content` property above the `style` property in the `customizations` code and then add the `strings` property block within this code. Now we can declare the exact text elements to replace within ThoughtSpot: - -[source,javascript] ----- -// ... -customizations: { - content: { - strings: { - "Liveboard": "Dashboard", - "SpotIQ": "Insights", - "Spotter": "AI Agent" - } - }, - style: { - ... - }, -}, ----- - -=== Replace icon sprites - -Icon sprite replacement requires creating a file of SVG icon definitions, the format for which is available in the xref:customize-icons.adoc[icon sprite customization documentation]. After you have the SVG file saved and accessible to ThoughtSpot, add the `iconSpriteUrl` property in the `customizations` block: - -[source,javascript] ----- -// ... -customizations: { - iconSpriteUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/icon-override1.svg", - content: { - strings: { - "Liveboard": "Dashboard", - "SpotIQ": "Insights", - "Spotter": "AI Agent" - } - }, - style: { - ... - }, -}, ----- - -== 05 - Menu items -ThoughtSpot menus are accessible in the top right corner with the ellipsis icon (`...`) or via a right-click on a chart axis or data point. The `...` menu is referred to as the *More* options menu. - -On Liveboards, a top menu for the Liveboard and a separate menu for each visualization is available. The menu from right-clicking a data point is referred to as the *Context menu*. - -=== Hiding or disabling items -Individual menu items are controlled by their capabilities and are referred to as *Actions*. The Visual Embed SDK reference guide for link:https://developers.thoughtspot.com/docs/Enumeration_Action[Actions^] contains a complete list of named capabilities. - -In the Playground, select the checkbox for `Modify available actions`. You'll see the following code in the code editor: - -[source,javascript] ----- - disabledActions: [], - disabledActionReason: "Reason for disabling", - // visibleActions: [], /* Removes all actions if empty array */ - hiddenActions: [], - /* Use either visibleActions or hiddenActions */ ----- - -If you want to show only a small set of selected menu items, use `visibleActions` (an allowlist) and comment out `hiddenActions` (a deny list). - -Let's show only the `DownloadAsPdf` action: - -[source,javascript] ----- - disabledActions: [], - disabledActionReason: "Reason for disabling", - visibleActions: [Action.DownloadAsPdf], /* Removes all actions if empty array */ - //hiddenActions: [], - /* Use either visibleActions or hiddenActions */ ----- - -When you click *Run*, the Liveboard reloads with only a single menu item in the *More* options menu as specified in the `visibleActions` array. - -[NOTE] -==== -The above example also hides the right-click context menu items, including the *Drill down* action (`Action.DrillDown`). -==== - -The `disabledActions` array keeps the item in the menu but grays it out, and shows `disabledActionReason` when hovering over the disabled action. - -=== Triggering hidden menu items with HostEvents -ThoughtSpot Visual Embed SDK defines two types of events: - -* link:https://developers.thoughtspot.com/docs/Enumeration_EmbedEvent[EmbedEvents^] - to listen to actions within ThoughtSpot components -* link:https://developers.thoughtspot.com/docs/Enumeration_HostEvent[HostEvents^] - to send messages to the ThoughtSpot components from the embedding application. - -If a menu item has been hidden, you can still send in a *HostEvent* to cause the same behavior. - -In the Playground, select the *Use Host Event* checkbox. You'll see the following code block in the code editor: - -[source,javascript] ----- - document.getElementById('tryBtn').addEventListener('click', e => { - // Trigger events can be added here to bind to try button click! - // eg use the Reload Event so that clicking on "Try event" button reloads the embed: - embed.trigger(HostEvent.Reload); - }); ----- - -The above code block adds a click event to the *Try Event* button above the preview panel in the Playground. Clicking *Try Event* triggers the `HostEvent`. - -Let's replace the default `Reload` event with `DownloadAsPdf`: - -[source,javascript] ----- - document.getElementById('tryBtn').addEventListener('click', e => { - // Trigger events can be added here to bind to try button click! - // eg use the Reload Event so that clicking on "Try event" button reloads the embed: - embed.trigger(HostEvent.DownloadAsPdf); - }); ----- - -Testing this requires the following steps: - -1. Click *Run* to reload the embedded component. -2. Click *Try Event*. - -You should see the PDF export modal dialog button pop up within the embedded component area. - -If you do not want the modal dialog to appear, you could instead use the ThoughtSpot REST API to accomplish the task either within the browser or in a back-end process. This allows for choosing vastly different behaviors than those allowed by the ThoughtSpot modal dialogs. - -=== Adding new menu items with custom actions -ThoughtSpot allows you to add new items called *xref:custom-actions.adoc[custom actions]* to the menu system, either to the *More* options menu on a given visualization of a Liveboard or the context menu that appears when a single point is right-clicked. - -The *Callback* custom actions require a three-part setup: - -1. Define the custom action within ThoughtSpot, with a particular *id*. -2. Assign the custom action to the visualization. -3. Add the `EmbedEvent.CustomAction` listener within the Visual Embed SDK code. - -To try it out, select the `Handle custom actions` checkbox. You'll see the following code block in the code editor: - -[source,javascript] ----- - .on(EmbedEvent.CustomAction, payload => { - const customActionId = 'insert Custom Action ID here'; - if (payload.id === customActionId || payload.data.id === customActionId) { - console.log('Custom Action event:', payload.data); - } - }) ----- - -Some link:https://developers.thoughtspot.com/docs/Enumeration_EmbedEvent[EmbedEvents^] such as `VizPointClick` fire off without involving the menu system and function similarly to context menu custom action. - -== 06 - Conclusion - -This tutorial has covered how to customize the UI and UX of ThoughtSpot's embedded components, but only within the Visual Embed SDK Playground. - -The next steps are to move this code into your application page and configure the CSP and CORS settings for your application's test environments, so you begin integrating ThoughtSpot directly into your application. diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-intro.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-intro.adoc index c2526f551..c199d0f96 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-intro.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-intro.adoc @@ -1,6 +1,5 @@ = ThoughtSpot Embedding fundamentals -:toc: true -:toclevels: 3 +:toc!: :page-title: ThoughtSpot Embeddig fundamentals :page-pageid: tse-fundamentals__intro @@ -53,3 +52,9 @@ This tutorial is split into smaller lessons, with specific objectives for each s |xref:tse-fundamentals-lesson-10.adoc[10 - Styling ThoughtSpot]|Explore styling options when embedding ThoughtSpot into your application. |xref:tse-fundamentals-lesson-11.adoc[11 - Summary and Next Steps]|Wrap up the course with a few miscellaneous topics and next steps. |=== + +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-01.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-01.adoc index 3cbc09d17..c40cdc12e 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-01.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-01.adoc @@ -1,6 +1,5 @@ = Introduction to ThoughtSpot Embedded -:toc: true -:toclevels: 3 +:toc!: :icons: font :page-title: Introduction to ThoughtSpot Embedded @@ -47,6 +46,9 @@ image:images/tutorials/tse-fundamentals/lesson-01-playground.png[Developer playg . If you don't already have one, create a link:https://www.thoughtspot.com/trial?tsref=ts-dev-training[ThoughtSpot Account, window=_blank]. -''' -[.text-right] -xref:tse-fundamentals-lesson-02.adoc[Next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-02.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-02.adoc index 82c2cefbf..60d6667f5 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-02.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-02.adoc @@ -1,6 +1,5 @@ = Set up for the course -:toc: true -:toclevels: 3 +:toc!: :page-title: Set up for the course :page-pageid: tse-fundamentals__lesson-02 @@ -78,4 +77,9 @@ image:images/tutorials/tse-fundamentals/lesson-02-download-git.png[Download from 3. Open the Visual Embed SDK Playground, modify content, and run the resulting code to become familiar with the code. 4. Download the code or clone the repository. -xref:tse-fundamentals-lesson-01.adoc[< prev] | xref:tse-fundamentals-lesson-03.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-03.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-03.adoc index 145401076..49cd95c62 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-03.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-03.adoc @@ -1,6 +1,5 @@ = Security setup -:toc: true -:toclevels: 3 +:toc!: :page-title: Security setup :page-pageid: tse-fundamentals__lesson-03 @@ -142,4 +141,9 @@ See the link:https://developers.thoughtspot.com/docs/trusted-auth[documentation] 2. Review the documentation for authentication link:https://developers.thoughtspot.com/docs/saml-sso[SAML SSO], link:https://developers.thoughtspot.com/docs/oidc-auth[OIDC], link:https://developers.thoughtspot.com/docs/trusted-auth[Trusted Authentication]. 3. Set the appropriate settings for your environment unless you are using the ThoughtSpot trial instance. -xref:tse-fundamentals-lesson-02.adoc[< prev] | xref:tse-fundamentals-lesson-04.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-04.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-04.adoc index 2360473a8..041c3d018 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-04.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-04.adoc @@ -1,6 +1,5 @@ = Start coding -:toc: true -:toclevels: 3 +:toc!: :page-title: Start coding :page-pageid: tse-fundamentals__lesson-04 @@ -172,4 +171,9 @@ At this point, you're ready to start adding content. 4. Open the application in a browser. 5. Open the developer tools and disable the cache. -xref:tse-fundamentals-lesson-03.adoc[< prev] | xref:tse-fundamentals-lesson-05.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-05.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-05.adoc index 7579d96e9..55af07228 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-05.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-05.adoc @@ -1,6 +1,5 @@ = Init and Search embedding -:toc: true -:toclevels: 3 +:toc!: :page-title: Init and Search embedding :page-pageid: tse-fundamentals__lesson-05 @@ -237,4 +236,9 @@ If you run into issues, you can reference the code in the `src` folder. * index.html * tse.js -xref:tse-fundamentals-lesson-04.adoc[< prev] | xref:tse-fundamentals-lesson-06.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-06.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-06.adoc index deaafb094..25a1a71e0 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-06.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-06.adoc @@ -1,6 +1,5 @@ = Embed Natural Language Search -:toc: true -:toclevels: 3 +:toc!: :page-title: Lesson 6 - Embed Natural Language Search :page-pageid: tse-fundamentals__lesson-06 @@ -137,4 +136,9 @@ If you run into issues, you can reference the code in the `src` folder. * index.html * tse.js -xref:tse-fundamentals-lesson-05.adoc[< prev] | xref:tse-fundamentals-lesson-07.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-07.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-07.adoc index 0ad51666e..42cf6ea95 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-07.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-07.adoc @@ -1,6 +1,5 @@ = Embed a Liveboard -:toc: true -:toclevels: 3 +:toc!: :page-title: Embed a Liveboard :page-pageid: tse-fundamentals__lesson-07 @@ -156,4 +155,9 @@ If you run into problems, you can look at the code in the `src` folder in this s * index.html * tse.js -xref:tse-fundamentals-lesson-06.adoc[< prev] | xref:tse-fundamentals-lesson-08.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-08.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-08.adoc index 658565a26..64d207308 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-08.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-08.adoc @@ -1,6 +1,5 @@ = Embed a Liveboard visualization -:toc: true -:toclevels: 3 +:toc!: :page-title: Embed a Liveboard Visualization :page-pageid: tse-fundamentals__lesson-08 @@ -124,4 +123,9 @@ If you run into problems, you can look at the code in the `src` folder in this s * index.html * tse.js -xref:tse-fundamentals-lesson-07.adoc[< prev] | xref:tse-fundamentals-lesson-09.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-09.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-09.adoc index f9b4ddaaa..b8212a8be 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-09.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-09.adoc @@ -1,6 +1,5 @@ = Embed the full ThoughtSpot application -:toc: true -:toclevels: 3 +:toc!: :page-title: Embed the Full ThoughtSpot Application :page-pageid: tse-fundamentals__lesson-09 @@ -201,4 +200,9 @@ If you run into problems, you can look at the code in the `src` folder in this s * index.html * tse.js -xref:tse-fundamentals-lesson-08.adoc[< prev] | xref:tse-fundamentals-lesson-10.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-10.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-10.adoc index a29a5fc67..040976076 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-10.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-10.adoc @@ -1,6 +1,5 @@ = Style embedded ThoughtSpot content -:toc: true -:toclevels: 3 +:toc!: :page-title: Styling Embedded ThoughtSpot Content :page-pageid: tse-fundamentals__lesson-10 @@ -89,4 +88,9 @@ The majority of the style is related to colors and fonts. The custom styles also * None -xref:tse-fundamentals-lesson-09.adoc[< prev] | xref:tse-fundamentals-lesson-11.adoc[next >] +++++ + +++++ diff --git a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-11.adoc b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-11.adoc index 210ed8613..b2d6ed35c 100644 --- a/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-11.adoc +++ b/modules/tutorials/pages/tse-fundamentals/tse-fundamentals-lesson-11.adoc @@ -1,6 +1,5 @@ = Course summary -:toc: true -:toclevels: 3 +:toc!: :page-title: Course Summary :page-pageid: tse-fundamentals__lesson-11 @@ -52,4 +51,9 @@ It's also where you enter support tickets and make feature recommendations. Finally, we post about upcoming events and important activities. It's a great place to get connected and stay informed. -xref:tse-fundamentals-lesson-10.adoc[< prev] +++++ + +++++ diff --git a/modules/tutorials/pages/tutorials-overview.adoc b/modules/tutorials/pages/tutorials-overview.adoc index a5e4acc45..ff2cd8ce4 100644 --- a/modules/tutorials/pages/tutorials-overview.adoc +++ b/modules/tutorials/pages/tutorials-overview.adoc @@ -14,7 +14,7 @@ xref:tse-fundamentals-intro.adoc[Begin Tutorial >] == ThoughtSpot UI elements and style customization Tutorial covering how to use customize ThoughtSpot UI elements and styles using Visual Embed SDK. -xref:style-customization_tutorial.adoc[Begin Tutorial >] +xref:style-customization_tutorial-intro.adoc[Begin Tutorial >] == ThoughtSpot REST API V2.0 Tutorial covering how to use ThoughtSpot V2.0 REST API, with examples in both Python and JavaScript. @@ -24,5 +24,4 @@ xref:rest-api-intro.adoc[Begin Tutorial >] == ThoughtSpot Spotter Tutorial covering how to integrate ThoughtSpot Spotter AI into a chatbot and embed conversational analytics. -xref:spotter-in-custom-chatbot.adoc[Begin Tutorial >] - +xref:spotter-in-custom-chatbot.adoc[Begin Tutorial >] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1f7de314b..9280c70f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20686,6 +20686,15 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-cookie": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", @@ -30174,7 +30183,7 @@ "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -45489,6 +45498,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==" + }, "js-cookie": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", @@ -52433,7 +52447,7 @@ "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, + "devOptional": true, "requires": {} }, "xdg-app-paths": { diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index baf0ec97c..a8949b96a 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -1147,6 +1147,130 @@ a.anchor { width: auto; } +// Tutorials overview cards — same boxDiv look, but sized to fill the +// grid column edge-to-edge (no width cap) so the three cards in a row +// read as evenly sized tiles rather than centered fixed-width boxes. +.tutorialCard { + flex: 1 1 280px; + width: 100%; + min-height: 280px; + margin: 10px; + padding: 28px; + + h5 { + overflow-wrap: break-word; + word-wrap: break-word; + } + + p { + overflow-wrap: break-word; + word-wrap: break-word; + } +} + +// Small uppercase label above the card title, e.g. "Tutorial". +.tutorialCardLabel { + font-size: 13px; + font-weight: 800; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--breadcrums-font-color); + margin: 0 0 8px; +} + +// "Start" call-to-action link at the bottom of the card. +.tutorialCardCta { + font-size: 13px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +// Center the tutorial-card rows as a group and tighten the gap between cards — +// .row/.col-md-4/.col-md-6 (grid.scss) add 15px of column padding on top of +// .tutorialCard's own margin, which otherwise leaves ~50px between cards. +.tutorial-cards-row { + justify-content: center; + + // Breathing room between the intro paragraph and the first card row — + // only the first row needs it; the row-to-row gap is already handled by .pb-10. + &:first-of-type { + margin-top: 24px; + } + + .col-md-4, + .col-md-6 { + padding: 0 8px; + } + + // .row (grid.scss) pulls the row in by -15px on each side to offset a + // Bootstrap-style container gutter that doesn't exist on this page. + // Override it here via the compound selector (higher specificity than + // the plain .row rule, so it wins regardless of stylesheet order). + // margin-right is larger than margin-left to visually balance the gutter + // — tuned by eye against .document-body-wrapper's padding, not derived + // from a formula, so re-check both edges if that padding ever changes. + &.row { + margin-left: 0; + margin-right: 60px; + } +} + +// Prev/Next footer buttons on tutorial content pages — prev sits on the left, +// next (or the final "Done") is pushed to the right via margin-left: auto so +// it stays right-aligned even when prev is absent (first page of a tutorial). +// (The SpotterCode floating assistant hides itself on tutorials pages — see +// isTutorialsPath in FloatingAssistant/index.tsx — so no space needs reserving +// here for it.) +.tutorial-pagination { + display: flex; + align-items: center; + margin-top: 32px; + padding-top: 20px; + border-top: 1px solid var(--border-color); + + // `.documentWrapper a, .documentWrapper a:active { color: var(--link-text-color); }` + // (Document/index.scss) has specificity 0,1,1 — a single `&__btn` class (0,1,0) + // loses to it in the resting state, so the text color only ever showed correctly + // under `:hover` (0,2,0). Compounding with the `a` element + parent class here + // raises these to 0,2,1 / 0,3,1 so they always win. + a.tutorial-pagination__btn { + display: inline-flex; + align-items: center; + padding: 8px 18px; + border: 1px solid var(--border-color); + border-radius: 6px; + font-size: 14px; + font-weight: 500; + color: var(--var-note-color); + text-decoration: none; + background: var(--body-background-color); + transition: border-color 0.15s ease; + + &:hover, + &:active { + border-color: var(--var-note-color); + color: var(--var-note-color); + } + } + + &__next { + margin-left: auto; + } + + a.tutorial-pagination__done { + background: var(--var-note-color); + border-color: var(--var-note-color); + color: #fff; + + &:hover, + &:active { + color: #fff; + opacity: 0.9; + } + } +} + .cardHeading { font-size: $font-size-h2; color: var(--primary-color) #1d232f; diff --git a/src/components/Breadcrums/index.tsx b/src/components/Breadcrums/index.tsx index 3a7449604..fbf5f28f6 100644 --- a/src/components/Breadcrums/index.tsx +++ b/src/components/Breadcrums/index.tsx @@ -6,6 +6,12 @@ import './index.scss'; type BreadcrumsProps = { pageid?: string; breadcrumsData: any; + // Individual walkthrough step pages use compound pageids + // ({subdir}__{finalSegment}) that only ever match the leaf node in + // nav-walkthroughs.adoc's own list, not the "Walkthroughs" entry that lives + // in nav.adoc — so there's no way back to the overview from a step page + // other than this explicit crumb. + showWalkthroughsCrumb?: boolean; }; const Breadcrums: React.FC = (props: BreadcrumsProps) => { @@ -23,6 +29,13 @@ const Breadcrums: React.FC = (props: BreadcrumsProps) => { Developer Guides + {props.showWalkthroughsCrumb && ( +
  • + + Walkthroughs + +
  • + )} {breadcrums.map(({ name, href }) => (
  • {href ? {name} : name} diff --git a/src/components/DevDocTemplate/index.tsx b/src/components/DevDocTemplate/index.tsx index 32b2bebb7..1c28e60dd 100644 --- a/src/components/DevDocTemplate/index.tsx +++ b/src/components/DevDocTemplate/index.tsx @@ -12,7 +12,7 @@ import { BiSearch } from '@react-icons/all-files/bi/BiSearch'; import { Analytics } from '@vercel/analytics/react'; import { Seo } from '../Seo'; import { queryStringParser, isPublicSite } from '../../utils/app-utils'; -import { passThroughHandler, fetchChild } from '../../utils/doc-utils'; +import { passThroughHandler, fetchChild, getPageIdFromHref } from '../../utils/doc-utils'; import Header from '../Header'; import SecondaryHeader, { DocCategory, CATEGORY_PAGEIDS, CATEGORY_NAV_ID } from '../SecondaryHeader'; import LeftSidebar from '../LeftSidebar'; @@ -184,6 +184,13 @@ const isVersionedIframe = VERSION_DROPDOWN.some( typeof window !== 'undefined' && new URLSearchParams(location.search).get('_iframe') === '1'; + // tutorials-overview and the walkthroughs overview are card-grid landing + // pages (like home) — no in-page nav to browse, so skip the left sidebar entirely + // and let the cards use the full width. + const hideLeftNav = ['tutorials-overview', 'walkthroughs'].includes( + curPageNode.pageAttributes.pageid, + ); + const isGQPlayGround = params[TS_PAGE_ID_PARAM] === CUSTOM_PAGE_ID.GQ_PLAYGROUND; const isPlayGround = @@ -195,18 +202,22 @@ const isVersionedIframe = VERSION_DROPDOWN.some( /* Build pageId → category map by parsing hrefs from each category's nav HTML. * This means writers only need to update nav-*.adoc — no TypeScript changes needed. - * Excludes the merged in-product nav, which isn't a real tab/category. */ + * Skips any merged nav key that isn't a real tab/category (e.g. the in-product nav, + * or a content-only nav file like nav-release-notes.adoc that a category points to + * via CATEGORY_NAV_ID rather than being a category itself) — otherwise `cat` ends up + * as a bogus DocCategory and CATEGORY_NAV_ID[activeCategory] later resolves to + * undefined, crashing on the `.startsWith` call in activeNavContent below. */ const pageIdToCategoryMap = React.useMemo(() => { if (typeof window === 'undefined') return {}; + const knownCategories = new Set(Object.keys(CATEGORY_NAV_ID)); const map: Record = {}; Object.entries(processedNavMap).forEach(([cat, html]) => { - if (cat === IN_PRODUCT_NAV_KEY) return; + if (cat === IN_PRODUCT_NAV_KEY || !knownCategories.has(cat)) return; const doc = new DOMParser().parseFromString(html as string, 'text/html'); doc.querySelectorAll('a[href]').forEach((a) => { - const href = a.getAttribute('href') || ''; - // hrefs are like /docs/pageid or /pageid — extract the last segment - const pageId = href.split('?')[0].split('/').filter(Boolean).pop(); - if (pageId) map[pageId] = cat as DocCategory; + const pageId = getPageIdFromHref(a.getAttribute('href')); + if (!pageId) return; + map[pageId] = cat as DocCategory; }); }); return map; @@ -447,7 +458,12 @@ const isVersionedIframe = VERSION_DROPDOWN.some( }); } - const shouldShowRightNav = params[TS_PAGE_ID_PARAM] !== HOME_PAGE_ID; + // tutorials-overview and the walkthroughs overview are card-grid landing + // pages like home — they have no in-page headings worth a right-nav TOC, and the + // extra 260px it reserves squashes the cards. + const shouldShowRightNav = + params[TS_PAGE_ID_PARAM] !== HOME_PAGE_ID && + !['tutorials-overview', 'walkthroughs'].includes(params[TS_PAGE_ID_PARAM]); Modal.setAppElement('#___gatsby'); const renderSearch = () => { const customStyles = { @@ -509,26 +525,29 @@ const isVersionedIframe = VERSION_DROPDOWN.some( const renderDocTemplate = () => ( <> {renderSearch()} -
    - { - setShowSearch(true); - if (!isMaxMobileResolution) setLeftNavOpen(false); - }} - /> -
    + {!hideLeftNav && ( +
    + { + setShowSearch(true); + if (!isMaxMobileResolution) setLeftNavOpen(false); + }} + /> +
    + )} {isAskDocsPage ? (
    @@ -547,6 +566,7 @@ const isVersionedIframe = VERSION_DROPDOWN.some( docTitle={docTitle} docContent={docContent} breadcrumsData={breadcrumsData} + showWalkthroughsCrumb={activeCategory === 'walkthroughs'} isPublicSiteOpen={isPublicSiteOpen} markdownBody={curPageNode.fields?.markdownBody} /> diff --git a/src/components/Document/helper.tsx b/src/components/Document/helper.tsx index 100bcd703..1efd03eed 100644 --- a/src/components/Document/helper.tsx +++ b/src/components/Document/helper.tsx @@ -3,6 +3,7 @@ import hljs from 'highlight.js'; import { FiCopy } from '@react-icons/all-files/fi/FiCopy'; import t from '../../utils/lang-utils'; import { getHTMLFromComponent } from '../../utils/react-utils'; +import { isTutorialsPath } from '../../utils/app-utils'; import selectors from '../../constants/selectorsContant'; export const enableCopyToClipboard = ( @@ -51,7 +52,7 @@ export const enableCopyToClipboard = ( export const customizeDocContent = () => { /* * Restructure code blocks to have a permanent header bar: - * lang label (left) · copy button (right) + * lang label (left) · Ask SpotterCode + copy button (right) * * Covers all listing/literal blocks regardless of [source,lang] annotation. * For annotated blocks, lang is read from code[data-lang] and the code element @@ -60,6 +61,12 @@ export const customizeDocContent = () => { * * Guard against double-processing on re-render. */ + // The SpotterCode assistant is hidden entirely on tutorials pages (see + // FloatingAssistant) — its "Ask SpotterCode" CTA here would just dispatch + // spotter-code-ask to nothing. Keep the copy button everywhere, including there. + const hideAskSpotterCode = + typeof window !== 'undefined' && isTutorialsPath(window.location.pathname); + document.querySelectorAll( '.listingblock>.content>pre, .literalblock>.content>pre', ).forEach((pre) => { @@ -83,14 +90,16 @@ export const customizeDocContent = () => { const rightGroup = document.createElement('div'); rightGroup.classList.add('code-block-header-actions'); - const ctaLink = document.createElement('button'); - ctaLink.classList.add('ctaButton'); - ctaLink.innerText = 'Ask SpotterCode'; - ctaLink.addEventListener('click', () => { - const code = copySource.innerText.trim(); - window.dispatchEvent(new CustomEvent('spotter-code-ask', { detail: { quotedText: code } })); - }); - rightGroup.appendChild(ctaLink); + if (!hideAskSpotterCode) { + const ctaLink = document.createElement('button'); + ctaLink.classList.add('ctaButton'); + ctaLink.innerText = 'Ask SpotterCode'; + ctaLink.addEventListener('click', () => { + const code = copySource.innerText.trim(); + window.dispatchEvent(new CustomEvent('spotter-code-ask', { detail: { quotedText: code } })); + }); + rightGroup.appendChild(ctaLink); + } /* Copy button — icon style */ const buttonElement = document.createElement('button'); diff --git a/src/components/Document/index.tsx b/src/components/Document/index.tsx index 6e3ab5942..aed5f40fa 100644 --- a/src/components/Document/index.tsx +++ b/src/components/Document/index.tsx @@ -7,6 +7,7 @@ import LinkableHeader from '../LinkableHeader'; import WasThisHelpful from '../WasThisHelpful'; import CopyPageDropdown from '../CopyPageDropdown'; import { HOME_PAGE_ID } from '../../configs/doc-configs'; +import { isTutorialsPath } from '../../utils/app-utils'; import parse, { HTMLReactParserOptions, domToReact, attributesToProps } from 'html-react-parser'; const Document = (props: { @@ -16,6 +17,7 @@ const Document = (props: { isPublicSiteOpen: boolean; shouldShowRightNav: boolean; breadcrumsData: any; + showWalkthroughsCrumb?: boolean; markdownBody?: string; }) => { const openAssistantWithQuote = (text: string) => { @@ -29,6 +31,10 @@ const Document = (props: { let mouseDownY = 0; const handleMouseUp = (e: MouseEvent) => { + // The SpotterCode assistant is hidden entirely on tutorials pages (see + // FloatingAssistant and Document/helper.tsx) — don't surface this CTA there either. + if (isTutorialsPath(window.location.pathname)) return; + const target = e.target as HTMLElement; if (target.closest('.selection-cta-button')) return; if (target.closest('.floating-assistant__panel, .floating-assistant__chip-ring')) return; @@ -177,6 +183,10 @@ const Document = (props: { }; const isHomePage = props.pageid === HOME_PAGE_ID; + // tutorials-overview and walkthroughs are card-grid landing pages with + // no real body text to copy, and the toolbar's float:right lands far from the + // (empty) breadcrumb above the wide card grid — just hide it here. + const hideCopyPage = ['tutorials-overview', 'walkthroughs'].includes(props.pageid); return (
    )} - {!isHomePage && props.isPublicSiteOpen && ( + {!isHomePage && !hideCopyPage && props.isPublicSiteOpen && (
    diff --git a/src/components/FloatingAssistant/index.tsx b/src/components/FloatingAssistant/index.tsx index e54a98e5a..22edad90d 100644 --- a/src/components/FloatingAssistant/index.tsx +++ b/src/components/FloatingAssistant/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { useFloatingAssistant } from '../../contexts/FloatingAssistantContext'; -import { isPublicSite } from '../../utils/app-utils'; +import { isPublicSite, isTutorialsPath } from '../../utils/app-utils'; import { CUSTOM_PAGE_ID } from '../../configs/doc-configs'; import { Alert, Icon, IconID, IconSize, IconColor, LoadingIndicator } from '@thoughtspot/radiant-react'; import '@thoughtspot/radiant-react/styles'; @@ -41,6 +41,12 @@ const AssistantAvatar = () => ( const FloatingAssistant: React.FC = () => { const [pageId, setPageId] = useState(getPageId); const [isEmbedded, setIsEmbedded] = useState(false); + // Tutorials pages have their own footer navigation (Previous/Next/Done) that this + // fixed, always-on-top panel has no awareness of — hide the widget there entirely + // rather than fight over screen space. + const [isTutorialsPage, setIsTutorialsPage] = useState( + () => typeof window !== 'undefined' && isTutorialsPath(window.location.pathname), + ); const { isOpen, setIsOpen, @@ -263,6 +269,14 @@ const FloatingAssistant: React.FC = () => { return () => window.removeEventListener('gatsby-route-update', handler as EventListener); }, []); + useEffect(() => { + const handler = (e: CustomEvent<{ location: Location }>) => { + setIsTutorialsPage(isTutorialsPath(e.detail.location.pathname)); + }; + window.addEventListener('gatsby-route-update', handler as EventListener); + return () => window.removeEventListener('gatsby-route-update', handler as EventListener); + }, []); + useEffect(() => { const handler = (e: CustomEvent<{ quotedText: string }>) => { setQuotedText(e.detail.quotedText); @@ -412,6 +426,7 @@ const FloatingAssistant: React.FC = () => { const isLandingPage = messages.length === 0 && !isLoading; if (pageId === CUSTOM_PAGE_ID.API_PLAYGROUND) return null; + if (isTutorialsPage) return null; return ( <> diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 763a8e248..f21d07b3e 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -85,17 +85,17 @@ const Header = (props: { ]; const renderDropdownItems = ( - items: { name: string; sub: string; link: string; icon: any }[], + items: { name: string; sub: string; link: string; icon: any; external?: boolean }[], onClose?: () => void, ) => items.map((item) => { const Icon = item.icon; + const isExternal = item.external !== false; return ( {item.name} {item.sub} - - - + {isExternal && ( + + + + )} ); }); diff --git a/src/components/LeftSidebar/NavContent.tsx b/src/components/LeftSidebar/NavContent.tsx index e9d6a8d96..2398f1b05 100644 --- a/src/components/LeftSidebar/NavContent.tsx +++ b/src/components/LeftSidebar/NavContent.tsx @@ -16,6 +16,7 @@ const NavContent = (props: { isDarkMode: boolean; setDarkMode: Function; searchClickHandler: Function; + isTutorialsNav?: boolean; }) => { return (
    @@ -159,6 +166,7 @@ const LeftSideBar = (props: { setDarkMode={props.setDarkMode} isDarkMode={props.isDarkMode} searchClickHandler={props.searchClickHandler} + isTutorialsNav={props.isTutorialsNav} />
    ); diff --git a/src/components/SecondaryHeader/index.tsx b/src/components/SecondaryHeader/index.tsx index 3121af577..3d8528a6a 100644 --- a/src/components/SecondaryHeader/index.tsx +++ b/src/components/SecondaryHeader/index.tsx @@ -9,19 +9,23 @@ import './index.scss'; export type DocCategory = | 'all' | 'guides' + | 'walkthroughs' | 'embedding' | 'rest-api' | 'mcp-server' | 'spottercode' + | 'tutorials' | 'whats-new'; export const CATEGORY_LABELS: Record = { all: 'All docs', guides: 'Developer guides', + walkthroughs: 'Guided Walkthroughs', embedding: 'Embedding', 'rest-api': 'REST APIs', 'mcp-server': 'MCP server', spottercode: 'SpotterCode', + tutorials: 'Tutorials', 'whats-new': "What's new", }; @@ -31,10 +35,12 @@ export const CATEGORY_LABELS: Record = { export const CATEGORY_LANDING: Record = { all: '/introduction', guides: '/introduction', + walkthroughs: '/tutorials/walkthroughs', embedding: '/getting-started', 'rest-api': '/rest-apis', 'mcp-server': '/mcp-integration', spottercode: '/SpotterCode', + tutorials: '/tutorials/tutorials-overview', 'whats-new': '/whats-new', }; @@ -45,11 +51,13 @@ export const CATEGORY_LANDING: Record = { export const CATEGORY_NAV_ID: Record = { all: 'nav', guides: 'nav', + walkthroughs: 'nav-walkthroughs', embedding: 'nav-embedding', 'rest-api': 'nav-rest-api', 'mcp-server': 'nav-mcp-server', spottercode: 'nav-spottercode', - 'whats-new': 'nav', + tutorials: 'nav-tutorials', + 'whats-new': 'nav-release-notes', }; /* @@ -70,6 +78,7 @@ export const CATEGORY_PAGEIDS: Record = { 'webhooks-overview', 'webhooks-ui', 'webhooks-comm-channel', 'webhooks-lb-schedule', 'webhooks-s3-integration', 'webhooks-gcs-storage', 'webhooks-lb-payload', 'webhooks-kpi', ], + walkthroughs: [], embedding: [ 'getting-started', 'tsembed', 'embed-liveboard', 'embed-a-viz', 'embed-ai-search-analytics', 'embed-spotter', 'embed-spotter-agent', @@ -114,6 +123,7 @@ export const CATEGORY_PAGEIDS: Record = { spottercode: [ 'SpotterCode', 'integrate-SpotterCode', 'spottercode-prompting-guide', ], + tutorials: [], 'whats-new': [ 'whats-new', 'fixed-issues', 'known-issues', 'deprecated-features', 'embed-sdk-changelog', 'mobile-sdk-changelog', @@ -137,6 +147,12 @@ const SecondaryHeader = (props: { const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const menuRef = useRef(null); + // 'tutorials' and 'walkthroughs' intentionally excluded — reachable from links + // inside nav.adoc's own sidebar rather than a secondary-header tab, and the + // secondary header/left sidebar are hidden altogether on their card-grid overview + // pages (see DevDocTemplate). Their category config (label/landing/nav id/pageids) + // still exists above — it's what makes nav-tutorials.adoc/nav-walkthroughs.adoc + // resolve as the active sidebar once you're on one of their pages. const categories: DocCategory[] = [ 'guides', 'embedding', 'rest-api', 'mcp-server', 'spottercode', 'whats-new', ]; diff --git a/src/utils/app-utils.ts b/src/utils/app-utils.ts index 337639e62..ae9fccfa5 100644 --- a/src/utils/app-utils.ts +++ b/src/utils/app-utils.ts @@ -64,6 +64,19 @@ export const removeTrailingSlash = (url: string) => { return url.replace(/\/$/, ''); }; +/** + * True for any tutorials-module page (/tutorials/... locally, /docs/tutorials/... + * in production) — path-segment check so it's robust to the {{navprefix}} prefix. + * Tutorials pages have their own pagination footer and no SpotterCode assistant + * (see FloatingAssistant), so SpotterCode-related affordances elsewhere (e.g. the + * "Ask SpotterCode" code-block CTA in Document/helper.tsx) should stay hidden there too. + * @param {string} pathname - location.pathname + * @returns {boolean} true if the path is under /tutorials/ + */ +export const isTutorialsPath = (pathname: string) => { + return pathname.split('/').filter(Boolean).includes('tutorials'); +}; + /** * Used to check if the docs portal open as public site or inside embed iframe * @param {string} queryParamStr - query string from location.search diff --git a/src/utils/doc-utils.ts b/src/utils/doc-utils.ts index ad56deac4..9b612de79 100644 --- a/src/utils/doc-utils.ts +++ b/src/utils/doc-utils.ts @@ -69,6 +69,24 @@ const getParentHref = (current) => { return null; }; +/** + * Reconstructs a page's pageid from an href found in nav content. Tutorials/ + * walkthroughs module pages use compound pageids ({subdirectory}__{finalSegment}, + * see getTutorialLinkFromEdge in gatsby-utils.js) while their nav links use the + * human-readable URL (/tutorials/{subdirectory}/{finalSegment}) — this bridges + * the two so matching works regardless of the {{navprefix}} substitution ('' on + * LOCAL, '/docs' otherwise), since only the trailing segments are inspected. + */ +export const getPageIdFromHref = (href?: string | null): string | null => { + if (!href) return null; + const segments = href.split('?')[0].split('/').filter(Boolean); + const lastSegment = segments.pop(); + if (!lastSegment) return null; + const tutorialsIdx = segments.indexOf('tutorials'); + const subdir = tutorialsIdx !== -1 ? segments[tutorialsIdx + 1] : undefined; + return subdir ? `${subdir}__${lastSegment}` : lastSegment; +}; + export const getBreadcrumsPath = (data: any, pageid?: string) => { if (!pageid) { return []; @@ -77,7 +95,8 @@ export const getBreadcrumsPath = (data: any, pageid?: string) => { return data.reduce((previous, current) => { if ( current.href === `?pageid=${pageid}` || - current.href === `/${SITE_PREFIX}/${pageid}` + current.href === `/${SITE_PREFIX}/${pageid}` || + getPageIdFromHref(current.href) === pageid ) { // To avoid having link for the same page we are setting href to null return [{ name: current.name, href: null }]; diff --git a/static/doc-images/images/mktng-tutorials/add-spotter.mp4 b/static/doc-images/images/mktng-tutorials/add-spotter.mp4 new file mode 100644 index 000000000..ce44792f2 Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/add-spotter.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/analytics.mp4 b/static/doc-images/images/mktng-tutorials/analytics.mp4 new file mode 100644 index 000000000..656349f82 Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/analytics.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/cbca.mp4 b/static/doc-images/images/mktng-tutorials/cbca.mp4 new file mode 100644 index 000000000..adca0a193 Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/cbca.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/dev-section.mp4 b/static/doc-images/images/mktng-tutorials/dev-section.mp4 new file mode 100644 index 000000000..b1d98c094 Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/dev-section.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/frame-video.mp4 b/static/doc-images/images/mktng-tutorials/frame-video.mp4 new file mode 100644 index 000000000..476ebca4e Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/frame-video.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/lb-embed-prompt.mp4 b/static/doc-images/images/mktng-tutorials/lb-embed-prompt.mp4 new file mode 100644 index 000000000..ea40e60ab Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/lb-embed-prompt.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/liveboard.mp4 b/static/doc-images/images/mktng-tutorials/liveboard.mp4 new file mode 100644 index 000000000..13a941a8a Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/liveboard.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/spotter-embed-playground.mp4 b/static/doc-images/images/mktng-tutorials/spotter-embed-playground.mp4 new file mode 100644 index 000000000..8a62f98db Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/spotter-embed-playground.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/spottercode-connector.mp4 b/static/doc-images/images/mktng-tutorials/spottercode-connector.mp4 new file mode 100644 index 000000000..2f14ee6bc Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/spottercode-connector.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/theme-builder-styling.mp4 b/static/doc-images/images/mktng-tutorials/theme-builder-styling.mp4 new file mode 100644 index 000000000..14490d6f6 Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/theme-builder-styling.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/themed-app.mp4 b/static/doc-images/images/mktng-tutorials/themed-app.mp4 new file mode 100644 index 000000000..7afffa5f7 Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/themed-app.mp4 differ diff --git a/static/doc-images/images/mktng-tutorials/working-spotter-embed.mp4 b/static/doc-images/images/mktng-tutorials/working-spotter-embed.mp4 new file mode 100644 index 000000000..5c471042d Binary files /dev/null and b/static/doc-images/images/mktng-tutorials/working-spotter-embed.mp4 differ