diff --git a/modules/ROOT/pages/common/nav.adoc b/modules/ROOT/pages/common/nav.adoc index 93b89a2c9..15fff7b11 100644 --- a/modules/ROOT/pages/common/nav.adoc +++ b/modules/ROOT/pages/common/nav.adoc @@ -41,11 +41,16 @@ *** link:{{navprefix}}/tutorials/tse-fundamentals/lesson-10[10 - Style embedded app] *** link:{{navprefix}}/tutorials/tse-fundamentals/lesson-11[11 - Course summary] ** link:{{navprefix}}/tutorials/style-customization/tutorial[Style customization] +** link:{{navprefix}}/tutorials/react-components/intro[React components] +*** link:{{navprefix}}/tutorials/react-components/lesson-01[01 - Initialize Visual Embed SDK] +*** 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/rest-api/intro[REST API] *** 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] -*** link:{{navprefix}}/tutorials/rest-api/lesson-04[04 - Browser JavaScript REST API implementation] +*** link:{{navprefix}}/tutorials/rest-api/lesson-04[04 - Event handling] ** Spotter *** link:{{navprefix}}/tutorials/spotter/integrate-into-chatbot[Integrate Spotter into your Chatbot] diff --git a/modules/ROOT/pages/customize-icons.adoc b/modules/ROOT/pages/customize-icons.adoc index 1bf01eb29..ceea024c1 100644 --- a/modules/ROOT/pages/customize-icons.adoc +++ b/modules/ROOT/pages/customize-icons.adoc @@ -8,14 +8,61 @@ You can customize the icons on a ThoughtSpot page using an icon sprite SVG file and load it from a Web server or CDN. -To override an icon: +== Before you begin -. Open the ThoughtSpot application page and locate the icon you want to replace. -.. Right-click on the icon and select *Inspect*. +* Identify the icons that you want to override. +.. On your ThoughtSpot instance, right-click on the icon and select *Inspect*. .. Inspect the `` element. .. Note the icon ID. -. Make sure you have SVG code for the icon that you want to use. For example, if you are using icons from an external site, copy its SVG code. -. Create an icon sprite file and add the SVG code and icon ID. The following code snippet shows how to override the chart icon (`rd-icon-chart`) on the *Answers* page. +* Add the SVG hosting domain to the following allowlists on the *Develop* > *Security Settings* page: +** xref:security-settings.adoc#csp-connect-src[CSP connect-src domains allowlist] +** xref:security-settings.adoc#csp-trusted-domain[CSP img-src domains allowlist] ++ +* xref:customize-icons.adoc#_try_it_out_in_the_embed_playground[Try out] the icon override code in the embed Playground. For ease of testing, the domain `cdn.jsdeliver.net` is already whitelisted on the ThoughtSpot link:https://try-everywhere.thoughtspot.cloud/v2/#/everywhere/playground/search[public Playground] and trial sites. + +== Create an icon override +The basic structure of an icon override file is shown in the following snippet: + +[source,svg] +---- + + + + + + +---- + +The `` portion within the `` tags is the definition of the actual drawing. + +You are defining a small icon, so it should fit within a square boundary and have a single solid color. + +There are many simple SVG icon examples available online, for example, the link:https://www.svgviewer.dev/[SVG viewer site, window=_blank]. + +You only need to copy the `` tags from your example SVG within the ` ` tags. + +=== viewBox property + +The link:https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox[viewBox property^] is in the order `min-x`, `min-y`, `width`, and `height`. + +The first two properties should be `0 0` while the second should match any `width` and `height` properties from the source of your SVG paths. + +The `width` and `height` properties may be added directly in the `` tag, or as part of `viewBox` property in that tag, or perhaps in another tag above ``. Use the values from your source SVG in the `viewBox` property of the `` element within your override file. + +=== fill property +You can add the `fill` property to the `` tag to define a different fill color than the default: + +[source,svg] +---- + +---- + +== Override an icon +To override an icon: + +. Create an icon sprite file with the SVG element, define the properties, and specify the icon ID. ++ +The following code snippet shows how to override the chart icon (`rd-icon-chart`) on the *Answers* page. + [source,HTML] ---- @@ -44,16 +91,32 @@ To override multiple icons, use the following format: + . Save the icon sprite file on a Web server. -. Add the SVG hosting domain to the *CSP connect-src domains* allowlist on the *Develop* > *Security Settings* page. For more information, see xref:security-settings.adoc#csp-connect-src[Security Settings]. . To override the icons on the ThoughtSpot page, specify the icon sprite URL in the `iconSpriteURL` property of the `customizations` object in Visual Embed SDK. + -The following code snippet uses the `icon-override1.svg` file hosted on the link:https://github.com/thoughtspot/custom-css-demo/blob/main/css-variables.css[Custom CSS demo GitHub Repo, window=_blank] to override the chart icon on the *Answers* page (`rd-icon-chart`): +For example, the following code snippets use the link:https://github.com/thoughtspot/custom-css-demo/blob/main/icon-override1.svg[icon-override1.svg] and link:https://github.com/thoughtspot/custom-css-demo/blob/main/alternate-spotter-icon.svg[alternate-spotter-icon.svg] files in the link:https://github.com/thoughtspot/custom-css-demo[Custom CSS demo GitHub Repo, window=_blank] to override the chart (`rd-icon-chart`) and Spotter (`rd-icon-spotter`) icons respectively: + + [source,JavaScript] ---- - customizations: { - iconSpriteUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/icon-override1.svg" - } + init({ + //... + customizations: { + // rd-icon-chart + iconSpriteUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/icon-override1.svg" + } + }); +---- + ++ +[source,JavaScript] +---- + init({ + //... + customizations: { + // rd-icon-spotter + iconSpriteUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/alternate-spotter-icon.svg" + } + }); ---- . Load the application page and check the icon. + @@ -61,58 +124,32 @@ The following figures show the icons before and after the override. + [width="100%" cols="6,6"] |====== -a|**Before** + +|| +2+|**Chart icon** on Answers +a|Before + image::./images/pre-icon-override.png[Before icon override] -a|**After** + +a|After + image::./images/post-icon-override.png[After icon override] -|====== - -== Creating an icon override -The basic structure of an icon override file is shown in the following snippet: - -[source,svg] ----- - - - - - - ----- - -The `` portion within the `` tags is the definition of the actual drawing. -You are defining a small icon, so it should fit within a square boundary and have a single solid color. - -There are many simple SVG icon examples available online, for example, the link:https://www.svgviewer.dev/[SVG viewer site, window=_blank]. - -You only need to copy the `` tags from your example SVG within the ` ` tags. - -=== viewBox property - -The link:https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox[viewBox property^] is in the order `min-x`, `min-y`, `width`, and `height`. - -The first two properties should be `0 0` while the second should match any `width` and `height` properties from the source of your SVG paths. +2+|**Spotter icon** +a|Before + +[.bordered] +image::./images/spotter-icon.png[Conversation embed] +a|After + +[.bordered] +image::./images/spotter-icon-customization.png[Spotter icon customization] -The `width` and `height` properties may be added directly in the `` tag, or as part of `viewBox` property in that tag, or perhaps in another tag above ``. Use the values from your source SVG in the `viewBox` property of the `` element within your override file. - -=== fill property -You can add the `fill` property to the `` tag to define a different fill color than the default: - -[source,svg] ----- - ----- +|====== -== Testing an icon override file +== Try it out in the Playground The +++Visual Embed SDK Playground +++ allows you to try out the icon customization framework. To view the code for customization: . Select the *Apply custom styles* checkbox in the Playground. + -The `customizations` code appears for CSS modifications appears in the code panel. +The `customizations` code for CSS modifications appears in the code panel. . Replace the `customization` section with the following code and click *Run* to view the results: + [source,JavaScript] @@ -121,9 +158,3 @@ The `customizations` code appears for CSS modifications appears in the code pane iconSpriteUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/icon-override1.svg" } ---- - -[NOTE] -==== -The `cdn.jsdeliver.net` domain is allowed on ThoughtSpot Embedded link:https://try-everywhere.thoughtspot.cloud/v2/#/everywhere/playground/search[public playground] and trial sites. -On your ThoughtSpot application instance, make sure to add the domain that will host the SVG file to xref:security-settings.adoc#csp-trusted-domain[the *CSP img-src domains* allowlist]. -==== \ No newline at end of file diff --git a/modules/ROOT/pages/rest-api-sdk-typescript.adoc b/modules/ROOT/pages/rest-api-sdk-typescript.adoc index 2a28fbd9e..cc474da9d 100644 --- a/modules/ROOT/pages/rest-api-sdk-typescript.adoc +++ b/modules/ROOT/pages/rest-api-sdk-typescript.adoc @@ -195,39 +195,32 @@ const test = async () => { }; ---- - == Supported versions -Note the version recommendations for your ThoughtSpot clusters: +Note the version recommendations for your ThoughtSpot instances: -[width="100%" cols="1,4"] +[width="100%" cols="2,2"] [options='header'] |==== -|SDK version|Minimum required version on ThoughtSpot cluster -|v2.0.2 a| ThoughtSpot Cloud: 9.6.0.cl + -ThoughtSpot Software: 9.8.0.sw -|v2.1.0| ThoughtSpot Cloud: 9.7.0.cl + -ThoughtSpot Software: 9.8.0.sw -|v2.2.0| ThoughtSpot Cloud: 9.8.0.cl + -ThoughtSpot Software: 9.8.0.sw -|v2.4.0| ThoughtSpot Cloud: 9.10.0.cl + -ThoughtSpot Software: 10.1.0.sw -|v2.4.1| ThoughtSpot Cloud: 9.10.5.cl + -ThoughtSpot Software: 10.1.0.sw -|v2.5.0| ThoughtSpot Cloud: 9.12.0.cl, 9.12.5.cl + -ThoughtSpot Software: 10.1.0.sw -|v2.6.0| ThoughtSpot Cloud: 10.1.0.cl + -ThoughtSpot Software: 10.1.0.sw -|v2.7.1| ThoughtSpot Cloud: 10.1.0.cl + -ThoughtSpot Software: 10.1.0.sw -|v2.9.0| ThoughtSpot Cloud: 10.3.0.cl + -ThoughtSpot Software: 10.7.0.sw -|v2.10.0| ThoughtSpot Cloud: 10.4.0.cl + -ThoughtSpot Software: 10.7.0.sw -|v2.11.0| ThoughtSpot Cloud: 10.5.0.cl + -ThoughtSpot Software: 10.7.0.sw +|ThoughtSpot release version|Recommended SDK version +a|* ThoughtSpot Cloud: 9.6.0.cl + +* ThoughtSpot Software: 9.8.0.sw | v2.0.2 or later +a|* ThoughtSpot Cloud: 9.7.0.cl + +* ThoughtSpot Software: 9.8.0.sw | v2.1.0 or later +a|* ThoughtSpot Cloud: 9.8.0.cl + +* ThoughtSpot Software: 9.8.0.sw | v2.2.0 or later +a|* ThoughtSpot Cloud: 9.10.0.cl | v2.4.0 or later +a|* ThoughtSpot Cloud: 9.10.5.cl | v2.4.1 or later +a|* ThoughtSpot Cloud release versions: + +** 9.12.0.cl + +** 9.12.5.cl + +** 10.1.0.cl + +** 10.3.0.cl + +** 10.4.0.cl + +** 10.5.0.cl + +* ThoughtSpot Software: 10.1.0.sw +| v2.11.1 or later |==== - For information about new features, breaking changes, and deprecated parameters, see xref:rest-apiv2-changelog.adoc[API changelog]. @@ -239,6 +232,10 @@ For a complete list of methods to use for API requests, see the following resour [options='header'] |==== |Category| Methods| HTTP Endpoints +.3+a| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/AIApi.md[AI, window=_blank] [beta betaBackground]^Beta^ +| `createConversation` | `POST /api/rest/2.0/ai/conversation/create` +| `sendMessage` | `POST /api/rest/2.0/ai/conversation/{conversation_identifier}/converse` +| `singleAnswer` | `POST /api/rest/2.0/ai/answer/create` .9+|link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/AuthenticationApi.md[Authentication, window=_blank] @@ -252,38 +249,31 @@ For a complete list of methods to use for API requests, see the following resour |`revokeToken` | `POST /api/rest/2.0/auth/token/revoke` |`validateToken`| `POST /api/rest/2.0/auth/token/validate` -.10+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/UsersApi.md[Users, window=_blank] -|`activateUser` | `POST /api/rest/2.0/users/activate` -|`changeUserPassword` | `POST /api/rest/2.0/users/change-password` -|`createUser` | `POST /api/rest/2.0/users/create` -|`deactivateUser`| `POST /api/rest/2.0/users/deactivate` -|`deleteUser` | `POST /api/rest/2.0/users/{user_identifier}/delete` -|`forceLogoutUsers` | `POST /api/rest/2.0/users/force-logout` -| `importUsers` | `POST /api/rest/2.0/users/import` -|`resetUserPassword`| `POST /api/rest/2.0/users/reset-password` -| `searchUsers` | `POST /api/rest/2.0/users/search` -| `updateUser` | `POST /api/rest/2.0/users/{user_identifier}/update` +.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/ConnectionsApi.md[Connections, window=_blank] -.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/SystemApi.md[System, window=_blank] -| `getSystemConfig` | `GET /api/rest/2.0/system/config` -| `getSystemInformation` | `GET /api/rest/2.0/system` -| `getSystemOverrideInfo` | `GET /api/rest/2.0/system/config-overrides` -| `updateSystemConfig` | `POST /api/rest/2.0/system/config-update` +| `createConnection` | `POST /api/rest/2.0/connection/create` +| `deleteConnection` | `POST /api/rest/2.0/connection/delete` +| `searchConnection` | `POST /api/rest/2.0/connection/search` +| `updateConnection` | `POST /api/rest/2.0/connection/update` -.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/OrgsApi.md[Orgs, window=_blank] -| `createOrg` | `POST /api/rest/2.0/orgs/create` -| `deleteOrg` | `POST /api/rest/2.0/orgs/{org_identifier}/delete` -| `searchOrgs` | `POST /api/rest/2.0/orgs/search` -| `updateOrg` | `POST /api/rest/2.0/orgs/{org_identifier}/update` +.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/CustomActionApi.md[Custom actions, window=_blank] -.6+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/TagsApi.md[Tags, window=_blank] +| `createCustomAction` | `POST /api/rest/2.0/customization/custom-actions` +| `deleteCustomAction` | `POST /api/rest/2.0/customization/custom-actions/{custom_action_identifier}/delete` +| `searchCustomActions` | `POST /api/rest/2.0/customization/custom-actions/search` +| `updateCustomAction` | `POST /api/rest/2.0/customization/custom-actions/{custom_action_identifier}/update` -| `assignTag` | `POST /api/rest/2.0/tags/assign` -| `createTag` | `POST /api/rest/2.0/tags/create` -| `deleteTag` | `POST /api/rest/2.0/tags/{tag_identifier}/delete` -| `searchTags` | `POST /api/rest/2.0/tags/search` -| `unassignTag` | `POST /api/rest/2.0/tags/unassign` -| `updateTag` | `POST /api/rest/2.0/tags/{tag_identifier}/update` +.3+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/DataApi.md[Data, window=_blank] +| `fetchAnswerData` | `POST /api/rest/2.0/metadata/answer/data` +| `fetchLiveboardData` | `POST /api/rest/2.0/metadata/liveboard/data` +| `searchData` | `POST /api/rest/2.0/searchdata` + +.5+|link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/DBTApi.md[DBT, window=_blank] +| `dbtConnection` | `POST /api/rest/2.0/dbt/dbt-connection` +| `dbtSearch` | `POST /api/rest/2.0/dbt/search` +| `generateSyncTml` | `POST /api/rest/2.0/dbt/generate-sync-tml` +| `generateTml` | `POST /api/rest/2.0/dbt/generate-tml` +| `updateDbtConnection` | `POST /api/rest/2.0/dbt/{dbt_connection_identifier}` .5+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/GroupsApi.md[Groups, window=_blank] @@ -293,6 +283,9 @@ For a complete list of methods to use for API requests, see the following resour | `searchUserGroups` | `POST /api/rest/2.0/groups/search` | `updateUserGroup` | `POST /api/rest/2.0/groups/{group_identifier}/update` +| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/LogApi.md[Log, window=_blank] +|`fetchLogs` | `POST /api/rest/2.0/logs/fetch` + .10+|link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/MetadataApi.md[Metadata, window=_blank] |`copyObject`| `POST /api/rest/2.0/metadata/copyobject` | `deleteMetadata` | `POST /api/rest/2.0/metadata/delete` @@ -305,10 +298,23 @@ For a complete list of methods to use for API requests, see the following resour |`importMetadataTMLAsync`|`POST /api/rest/2.0/metadata/tml/async/import` | `searchMetadata` | `POST /api/rest/2.0/metadata/search` +.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/OrgsApi.md[Orgs, window=_blank] +| `createOrg` | `POST /api/rest/2.0/orgs/create` +| `deleteOrg` | `POST /api/rest/2.0/orgs/{org_identifier}/delete` +| `searchOrgs` | `POST /api/rest/2.0/orgs/search` +| `updateOrg` | `POST /api/rest/2.0/orgs/{org_identifier}/update` + .2+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/ReportsApi.md[Reports, window=_blank] | `exportAnswerReport` | `POST /api/rest/2.0/report/answer` | `exportLiveboardReport` | `POST /api/rest/2.0/report/liveboard` +.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/RolesApi.md[Roles, window=_blank] + +| `createRole` | `POST /api/rest/2.0/roles/create` +| `deleteRole` | `POST /api/rest/2.0/roles/{role_identifier}/delete` +| `searchRoles` | `POST /api/rest/2.0/roles/search` +| `updateRole` | `POST /api/rest/2.0/roles/{role_identifier}/update` + .4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/SecurityApi.md[Security, window=_blank] | `assignChangeAuthor` | `POST /api/rest/2.0/security/metadata/assign` @@ -316,13 +322,40 @@ For a complete list of methods to use for API requests, see the following resour | `fetchPermissionsOnMetadata` | `POST /api/rest/2.0/security/metadata/fetch-permissions` | `shareMetadata` | `POST /api/rest/2.0/security/metadata/share` -.3+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/DataApi.md[Data, window=_blank] -| `fetchAnswerData` | `POST /api/rest/2.0/metadata/answer/data` -| `fetchLiveboardData` | `POST /api/rest/2.0/metadata/liveboard/data` -| `searchData` | `POST /api/rest/2.0/searchdata` +.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/SchedulesApi.md[Schedules, window=_blank] +| `createSchedule` | `POST /api/rest/2.0/schedules/create` +| `deleteSchedule` | `POST /api/rest/2.0/schedules/{schedule_identifier}/delete` +| `searchSchedules` | `POST /api/rest/2.0/schedules/search` +| `updateSchedule` | `POST /api/rest/2.0/schedules/{schedule_identifier}/update` + +.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/SystemApi.md[System, window=_blank] +| `getSystemConfig` | `GET /api/rest/2.0/system/config` +| `getSystemInformation` | `GET /api/rest/2.0/system` +| `getSystemOverrideInfo` | `GET /api/rest/2.0/system/config-overrides` +| `updateSystemConfig` | `POST /api/rest/2.0/system/config-update` + + +.6+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/TagsApi.md[Tags, window=_blank] + +| `assignTag` | `POST /api/rest/2.0/tags/assign` +| `createTag` | `POST /api/rest/2.0/tags/create` +| `deleteTag` | `POST /api/rest/2.0/tags/{tag_identifier}/delete` +| `searchTags` | `POST /api/rest/2.0/tags/search` +| `unassignTag` | `POST /api/rest/2.0/tags/unassign` +| `updateTag` | `POST /api/rest/2.0/tags/{tag_identifier}/update` + +.10+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/UsersApi.md[Users, window=_blank] +|`activateUser` | `POST /api/rest/2.0/users/activate` +|`changeUserPassword` | `POST /api/rest/2.0/users/change-password` +|`createUser` | `POST /api/rest/2.0/users/create` +|`deactivateUser`| `POST /api/rest/2.0/users/deactivate` +|`deleteUser` | `POST /api/rest/2.0/users/{user_identifier}/delete` +|`forceLogoutUsers` | `POST /api/rest/2.0/users/force-logout` +| `importUsers` | `POST /api/rest/2.0/users/import` +|`resetUserPassword`| `POST /api/rest/2.0/users/reset-password` +| `searchUsers` | `POST /api/rest/2.0/users/search` +| `updateUser` | `POST /api/rest/2.0/users/{user_identifier}/update` -| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/LogApi.md[Log, window=_blank] -|`fetchLogs` | `POST /api/rest/2.0/logs/fetch` .9+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/VersionControlApi.md[Version control, window=_blank] | `commitBranch` | `POST /api/rest/2.0/vcs/git/branches/commit` @@ -334,43 +367,4 @@ For a complete list of methods to use for API requests, see the following resour | `searchConfig` | `POST /api/rest/2.0/vcs/git/config/search` | `updateConfig` | `POST /api/rest/2.0/vcs/git/config/update` | `validateMerge` | `POST /api/rest/2.0/vcs/git/branches/validate` - -.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/ConnectionsApi.md[Connections, window=_blank] - -| `createConnection` | `POST /api/rest/2.0/connection/create` -| `deleteConnection` | `POST /api/rest/2.0/connection/delete` -| `searchConnection` | `POST /api/rest/2.0/connection/search` -| `updateConnection` | `POST /api/rest/2.0/connection/update` - -.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/CustomActionApi.md[Custom actions, window=_blank] - -| `createCustomAction` | `POST /api/rest/2.0/customization/custom-actions` -| `deleteCustomAction` | `POST /api/rest/2.0/customization/custom-actions/{custom_action_identifier}/delete` -| `searchCustomActions` | `POST /api/rest/2.0/customization/custom-actions/search` -| `updateCustomAction` | `POST /api/rest/2.0/customization/custom-actions/{custom_action_identifier}/update` - -.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/SchedulesApi.md[Schedules, window=_blank] -| `createSchedule` | `POST /api/rest/2.0/schedules/create` -| `deleteSchedule` | `POST /api/rest/2.0/schedules/{schedule_identifier}/delete` -| `searchSchedules` | `POST /api/rest/2.0/schedules/search` -| `updateSchedule` | `POST /api/rest/2.0/schedules/{schedule_identifier}/update` - -.4+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/RolesApi.md[Roles, window=_blank] - -| `createRole` | `POST /api/rest/2.0/roles/create` -| `deleteRole` | `POST /api/rest/2.0/roles/{role_identifier}/delete` -| `searchRoles` | `POST /api/rest/2.0/roles/search` -| `updateRole` | `POST /api/rest/2.0/roles/{role_identifier}/update` - -.5+|link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/DBTApi.md[DBT, window=_blank] -| `dbtConnection` | `POST /api/rest/2.0/dbt/dbt-connection` -| `dbtSearch` | `POST /api/rest/2.0/dbt/search` -| `generateSyncTml` | `POST /api/rest/2.0/dbt/generate-sync-tml` -| `generateTml` | `POST /api/rest/2.0/dbt/generate-tml` -| `updateDbtConnection` | `POST /api/rest/2.0/dbt/{dbt_connection_identifier}` - -.3+| link:https://github.com/thoughtspot/rest-api-sdk/blob/release/sdks/typescript/AIApi.md[Data, window=_blank] -| `createConversation` | `POST /api/rest/2.0/ai/conversation/create` -| `sendMessage` | `POST /api/rest/2.0/ai/conversation/{conversation_identifier}/converse` -| `singleAnswer` | `POST /api/rest/2.0/ai/answer/create` |==== diff --git a/modules/tutorials/pages/react-components/react-components_intro.adoc b/modules/tutorials/pages/react-components/react-components_intro.adoc index a31e54a7b..4cdc5e771 100644 --- a/modules/tutorials/pages/react-components/react-components_intro.adoc +++ b/modules/tutorials/pages/react-components/react-components_intro.adoc @@ -1,5 +1,204 @@ = ThoughtSpot React Components Tutorial -:page-pageid: react-components_intro -:description: This is a self-guided course on the ThoughtSpot Visual Embed SDK React Components :toc: true :toclevels: 2 + +:page-pageid: react-components__intro +:description: This is a self-guided course on the ThoughtSpot Visual Embed SDK React Components + + +ThoughtSpot provides xref:embed-ts-react-app.adoc[React components] as part of the link:https://github.com/thoughtspot/visual-embed-sdk[Visual Embed SDK^]. + +There is also a link:https://github.com/thoughtspot/rest-api-sdk/tree/release/sdks/typescript[REST API SDK available in TypeScript^] which can be included in any React app. + +This tutorial walks through the process of installing and integrating the ThoughtSpot React components and REST API SDK into an link:https://github.com/thoughtspot/embed-example-react-app[existing React app^]. + +The tutorial will discuss common React app features and how to integrate the ThoughtSpot technologies into the React patterns. + +Please download the link:https://github.com/thoughtspot/embed-example-react-app[example app from GitHub^] and follow the initial setup commands listed in the README within the GitHub repository. + +== Getting started with React +link:https://react.dev/learn/thinking-in-react[React^] is a widely used web application framework. + +There are many great resources for understanding React itself, including the link:https://react.dev/learn/thinking-in-react[React project's own website]. In the tutorial, React concepts will be briefly explained in relation to integrating ThoughtSpot components within the link:https://github.com/thoughtspot/embed-example-react-app[embedding React app^]. + +React projects almost always use many different packages on top of the baseline "React" install. + +Within the tutorial, all packages will be given a brief explanation, with links to the respective project documentation. + +== Routes and pages +*Routes* are URLs that load different "pages" within the app. + +In some web application systems, a particular URL will load content from a distinct "page" that exists somewhere on the web server. + +React apps are typically "single-page apps": rather than there being many separate "actual" HTML pages loaded by the web browser, an initial page is loaded and then updates are made to that "single page". + +*Routing* in React defines what components are retrieved for a given URL to update the "single page". + +ThoughtSpot provides various types of components that load objects using specific IDs of the content in the ThoughtSpot application. + +To make a flexible system, a page route must return the component and pass the ID of the content to load into that component. + +=== React Router +The most common package for routing is *link:https://reactrouter.com/start/library/routing[react-router^]*, with most examples importing `react-router-dom` in their `App.tsx` file. + +An example of a dynamic page route using React Router might look like: + +[source,typescript] +---- +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + }, + { + path: "dashboard/:id", + element: , + }, +]); +---- + +where the `:id` portion of the path will be passed on to the component as one of the *props*. + +If your app uses React Router, please work with your team to understand how to create and use dynamic variables in the routes and pass them into components. + +The app in this tutorial instead uses Next.js for routing. + +=== Next.js +The tutorial app uses link:https://nextjs.org/docs/app/getting-started/project-structure[Next.js^], a platform built on top of React + +Next.js provides automatic *file-based* routing via defined patterns and directory structures. + +Under the `app` directory, any subdirectory *automatically becomes a URL route* with the same name, with the `page.tsx` file inside defining the returned component. + +Adding square brackets around a subdirectory name like `[variableName]` creates dynamic page route with the name in brackets sent to the component defined in `page.tsx`. + +== Tutorial app structure +The tutorial walks through building out an link:https://github.com/thoughtspot/embed-example-react-app[app^] using link:https://nextjs.org/docs/app/getting-started/project-structure[Next.js^]. + +=== Top-level subdirectory structure +The app has three main directories, per a standard Next.js app structure: + +1. `app`: distinct app routes generated from the `page.tsx` of each subdirectory +2. `components`: shared components in `.tsx` files that can be called by multiple other `page.tsx` files +3. `lib`: other constant files + +[.widthAuto] +image::images/tutorials/react-components/next-js-app-structure.png[App structure] + +=== lib/constants.ts: shared configurations +link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/lib/constants.ts[app/constants.ts^] is a static file for declaring shared properties that might be re-used by any other page within the React app. + +The `constants` const is declared and exported with several properties, the most important of which is `tsURL`, because it configures the ThoughtSpot instance from which the content will load. + +[source,typescript] +---- +export const constants = { + tsURL: "https://{}.thoughtspot.cloud", + username: "", + password: "", + appTopTitle: "My App", + appTopIconUrl: "/images/ts.png", + primaryColorCode: "rgb(0, 0 , 0)", + secondaryColorCode: "rgb(255, 255, 255)" +}; +---- + +You are welcome to add any additional properties you might want to use. + +Other pages import the code from `constants.ts` using a standard React import: + +[source,typescript] +---- +import {constants, cssFiles} from "@/lib/constants"; +---- + +=== app/layout.tsx: the React app itself +link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/app/layout.tsx[app/layout.tsx^] is a standard Next.js file that defines the overall layout of the app. + +It returns the `RootLayout()` function for the React framework, into with all other components will be added in as `{children}`. + +The actual JSX that is returned is simple, as the tutorial app is composed of only a top menu and a full-width area for displaying ThoughtSpot content: + +[,tsx] +---- + + <> + + +
{children}
+
+ + + +---- + +Note that everything above is a React *component*, defined in other files and *included* at the top of `layout.tsx`. + +=== app/page.tsx: the default page +`link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/app/page.tsx[app/page.tsx^]` is the page that contains the initial body that is loaded when a user comes to the app, without any other pages: + +[,tsx] +---- +export default function Home() { + return ( +
+
+

Welcome to the ThoughtSpot Embedding Example

+

+ This application demonstrates some of the basic embedding techniques possible using React and the + ThoughSpot SDK. +

+

 

+... +
+
+ ); +} +---- + +You are welcome to put anything in this page, but it is really a placeholder that would instead be taken by the real app you embed ThoughtSpot components into. + +=== components/TopNavBar.tsx: the top menu bar +`link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/components/TopNavBar.tsx[components/TopNavBar.tsx^]` is a component page that defines the top menu bar within the app. + +You'll notice it imports several components from link:https://flowbite.com/[Flowbite^], a commonly used React package with many available UI components, along with the `Link` component from Next.js. Do not feel bound to use any components in the tutorial, they are simply to show how to integrate the ThoughtSpot components with all of the various other React components and packages in use within a normal application project. + +[,tsx] +---- +"use client"; + +import Link from "next/link"; +import {Dropdown, Navbar} from "flowbite-react"; + +import styles from "./TopNavBar.module.css"; + +import {constants} from "@/lib/constants"; + +interface NavBarProps { +} +---- + +`TopNavBar.tsx` is where the links to routes for the menu pages are defined: + +[,tsx] +---- + + + Dashboards + + + Data Chat + + +---- + +Again, this is simply to provide a simple example of how you will integrate routes to pages that display ThoughtSpot components within your own app. + +=== 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 new file mode 100644 index 000000000..ba0d4a6ff --- /dev/null +++ b/modules/tutorials/pages/react-components/react-components_lesson-01.adoc @@ -0,0 +1,93 @@ += Initializing ThoughtSpot Embed SDK +:toc: true +:toclevels: 2 + +:page-pageid: react-components__lesson-01 +:description: Initializing ThoughtSpot Embed SDK within React app + + +The Visual Embed SDK provides an `init()` *function* that handles authentication and other shared configuration options for all SDK components. + +In your code, you need to call `init()` before any other ThoughtSpot components are loaded. + +In the tutorial app, which is *only* displaying ThoughtSpot content, the `init()` function is run as soon as the app is loaded, in the `layout.tsx` file, via the `ThoughtSpotEmbed` component. + +The link:https://developers.thoughtspot.com/docs/Interface_EmbedConfig[various configurations^] can be used exactly as defined within the standard Visual Embed SDK documentation, because the `init()` function is called within the TypeScript code of the component page, outside of the React component portions defined in JSX/TSX. + +== components/ThoughtSpotEmbed.tsx: running init() one time for all components +The pattern within `link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/components/ThoughtSpotEmbed.tsx[components/ThoughtSpotEmbed.tsx^]` is important, because it defines any initial configurations and other functions, and runs the `init()` function only once. + +At the bottom, we run the `tsInitialize()` function once, and then return a `div` with `id=ts-embed` that will be used as the container for any individual ThoughtSpot embed components loaded by the app later: + +[,tsx] +---- + tsInitialize(); + +return ( +
+
+ {children} +
+
+); +---- + +The `tsInitialize()` function wraps around the entire process of calling the `init()` function from the Visual Embed SDK (note that authentication has not been implemented in this example): + +[source,typescript] +---- + const tsInitialize = () => { + console.log("Initializing ThoughtSpot SDK"); + + // init() function defines basic configuration and authentication for all ThoughtSpot embed components + // See https://developers.thoughtspot.com/docs/Interface_EmbedConfig for all configurations + const ee = init({ + thoughtSpotHost: constants.tsURL, + authType: AuthType.None, + username: constants.username, + getAuthToken: () => { + return getAuthToken(constants.username); + }, + callPrefetch: true, + customizations: {}, + } as EmbedConfig); + + // Checks for Auth process completed as expected + if (ee) { + ee.on(AuthStatus.SUCCESS, () => { + console.log("Success"); + }) + .on(AuthStatus.SDK_SUCCESS, () => { + console.log("SDK Success"); + }) + .on(AuthStatus.FAILURE, (reason) => { + console.log("Failure: " + reason); + }); + } + }; +---- + +== Common customizations in the init +As mentioned above, link:https://developers.thoughtspot.com/docs/Interface_EmbedConfig[many configuration options] can be specified as part of the `init()` function. + +Common configurations include xref:prefetch-and-cache.adoc[prefetch for better performance] and the `link:https://developers.thoughtspot.com/docs/Interface_EmbedConfig#_autologin[autoLogin: true]` property when using xref:trusted-authentication.adoc[Trusted Authentication]. + +You'll notice in the app options for some of the most common set of configurations involve link:https://developers.thoughtspot.com/docs/tutorials/style-customization/tutorial[style customizations^] such as CSS and text string replacement: + +[source,JavaScript] +---- + customizations: { + style: { + customCSS: customCss, + }, + content: { + strings: stringsCustom + }, + iconSpriteUrl: iconUrl +} +---- + +''' + +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 new file mode 100644 index 000000000..6429ee6be --- /dev/null +++ b/modules/tutorials/pages/react-components/react-components_lesson-02.adoc @@ -0,0 +1,159 @@ += ThoughtSpot component pages +:page-pageid: react-components__lesson-02 +:description: Build a page that returns a ThoughtSpot component +:toc: true +:toclevels: 2 + +== 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. + +As mentioned in the introduction, Next.js uses *square brackets in a directory name* to denote a *variable route*. The value placed in the URL after `/dashboard/` will be available as the variable `dashboardId` in the `page.tsx` code. + +Note the URL says *dashboard*, rather than Liveboard. When embedding ThoughtSpot, you can present every aspect of ThoughtSpot as completely customized, matching your own app's naming and design preferences. + +=== Imports +The `/app/dashboard/[dashboardId]/page.tsx` file starts with imports: some from React and some from the ThoughtSpot Visual Embed SDK. + +[source,typescript] +---- +import { useEffect, useState } from "react"; + +import { + LiveboardEmbed, + useEmbedRef, +} from "@thoughtspot/visual-embed-sdk/react"; +---- + +`LiveboardEmbed` is the component provided by ThoughtSpot to display a Liveboard object. + +The React components of the ThoughtSpot Visual Embed SDK *wrap* around the embedded ThoughtSpot components, which utilize modern iFrame techniques to provide the full ThoughtSpot interface within a page. + +The `link:https://react.dev/reference/react/useEffect[useEffect^]`, `link:https://react.dev/reference/react/useState[useState^]`, and `useEmbedRef` Hooks are used for managing aspects of React that do not need to be re-run every time a component is refreshed. Without proper use of these React features, the ThoughtSpot components may refresh or be rebuilt by the React application at unnecessary times. + +=== Pass Liveboard ID to the component +React components have link:https://react.dev/learn/passing-props-to-a-component[props^], which are *properties* of the component. + +The most important prop of the `LiveboardEmbed` is the `liveboardId` prop that determines which object the component loads from ThoughtSpot. + +The `page.tsx` we are defining will return a fully configured `LiveboardEmbed` component as part of a larger `Dashboard` component. + +The `dashboardId` value needs to pass in from the URL to the `Dashboard` component. As mentioned earlier, in this example using Next.js, the directory name with `[dashboardId]` provides the value using the variable name within the brackets. Other routing systems will provide their own mechanism for receiving values from a URL. + +In the following code, the `Props` interface captures the `dashboardId` and passes it into the `const Dashboard` definition, making the value available within the `Dashboard` component via `params.dashboardId`. + +[source,typescript] +---- + // Defines the expectation of the dashboardId variable from the URL + interface Props { + params: { dashboardId: string }; + } + + const Dashboard = ({ params }: Props) => { + ... +---- + +=== Set and update state +React components are designed to react to changes in *link:https://react.dev/learn/reacting-to-input-with-state[state^]*. + +By using the link:https://react.dev/reference/react/useState[useState^] function imported at the beginning of the code, a `dashboardId` state variable is created, with a `setDashboardId` update function: + +[source,typescript] +---- +const [dashboardId, setDashboardId] = useState(""); +---- + +The link:https://react.dev/reference/react/useEffect[useEffect^] Hook is necessary for proper synchronization of elements that are external to the React app. The first usage wraps around calling the `setDashboardId` state function. + +[source,typescript] +---- +const Dashboard = ({ params }: Props) => { + const [dashboardId, setDashboardId] = useState(""); + + // Interactions with systems outside of React app get wrapped in useEffect() + useEffect(() => { + setDashboardId(params.dashboardId); + { + console.log(`Dashboard Id: ${params.dashboardId}`); + setDashboardId(params.dashboardId); + } + }, [params.dashboardId]); // Only runs when dashboardId changes + +... +---- + +=== Create the ThoughtSpot embed component +The final aspect of establishing proper interaction of the React app and the ThoughtSpot embed component prior to returning the component itself is declaring the `embedRef` const: + +[source,typescript] +---- + const embedRef = useEmbedRef(); +---- + +With the `embedRef` declared, the `LiveboardEmbed` component can be declared along with any necessary props: + +[source,typescript] +---- +... + // Required for referencing the LiveboardEmbed that is created in other code + const embedRef = useEmbedRef(); + + return ( + (dashboardId && ( + // ThoughtSpot LiveboardEmbed component with config properties. See https://developers.thoughtspot.com/docs/Interface_LiveboardViewConfig + + + )) ||
No dashboard ID set.
+ ); +}; + +export default Dashboard; +---- + +Note that the `return` block above uses a standard link:https://react.dev/learn/conditional-rendering[conditional rendering^] pattern involving JavaScript's conditional operators, to provide the alternative `
No dashboard Id set.
` if the component was not provided a value for the `dashboardId`. + +Production code might include additional logical checks to be sure all necessary information has been provided before attempting to render a ThoughtSpot embed component. + +=== Configuration options as component props +The Visual Embed SDK defines a large number of properties to configure each component. For example, link:https://developers.thoughtspot.com/docs/Interface_LiveboardViewConfig[LiveboardViewConfig^] for the LiveboardEmbed component. + +The properties are sent as an object in the second argument of the component constructor in the JavaScript components: + +[source,JavaScript] +---- +const liveboardEmbed = new LiveboardEmbed(document.getElementById('ts-embed'), { + frameParams: { + width: '100%', + height: '100%', + }, + liveboardId: '<%=liveboardGUID%>', +}); +---- +Translating the Visual Embed SDK JavaScript documentation into the form used by the React components is relatively simple. + +The React components have the same properties available as *props* of the component, rather than using a separate config object: + +[source,typescript] +---- + +---- + +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 new file mode 100644 index 000000000..c156cdc34 --- /dev/null +++ b/modules/tutorials/pages/react-components/react-components_lesson-03.adoc @@ -0,0 +1,254 @@ += 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 + +The ThoughtSpot component page from the previous lesson was designed to display any Liveboard with a valid ID as part of the URL. + +While you can hardcode a set of known objects with their names and IDs into a navigation menu, ThoughtSpot's true power is allowing users to ask new questions and build their answers and Liveboards. + +The ThoughtSpot REST API provides the `xref:rest-api-v2-metadata-search.adoc[/metadata/search]` endpoint for listing out the data objects on the ThoughtSpot instance, with a number of options for filtering or retrieving more details. + +The REST API automatically filters the results based on the permissions of the user who makes the request. + +The challenge is to ensure that the SDK uses the session in the browser or a valid bearer token for the user logged into the app. + +== /app/dashboard/page.tsx: a tabular menu of Liveboards +The example app has a dedicated link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/app/dashboard/page.tsx[/app/dashboard/page.tsx^] to display a tabular menu of available Liveboards. + +The following steps are required for this page: + +1. Make a REST API request to ThoughtSpot to retrieve the details of the objects +2. Build the table from the results and construct links to the link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/app/dashboard/%5BdashboardId%5D/page.tsx[/dashboard/{dashboardId^] page to display the Liveboard. + +=== Create state variables +The menu page provides some UI options to configure the REST API request that generates the menu items. The pattern in React is for options to update shared link:https://react.dev/learn/reacting-to-input-with-state[state variables^] using the link:https://react.dev/reference/react/useState[useState()^] function, with updates triggered within components when the associated `setState()` function is called. + +The page uses the following three state variables, with the default set by the initial `useState()` function: + +[source,typescript] +---- +export default function DashboardList() { + const [metadataData, setMetadataData] = useState(); + const [showMyItems, setShowMyItems] = useState(false); + const [authorName, setAuthorName] = useState(''); +... +---- + +== ThoughtSpot REST API TypeScript SDK +ThoughtSpot provides a link:https://developers.thoughtspot.com/docs/rest-api-sdk-typescript#_get_started[TypeScript REST API SDK^] that can be installed via Node and added to any React app. + +The SDK allows calling any of the V2.0 REST API endpoints and returns in a native JSON format, making it ideal for retrieving the details of objects that a user can see. + +=== Import REST API SDK +To use the REST API SDK, import `createConfiguration`, `ServerConfiguration` and `ThoughtSpotRestApi` from `@thoughtspot/rest-api-sdk`: + +[source,typescript] +---- +"use client"; + +import {useEffect, useState} from "react"; +import Link from 'next/link'; + +// Menu page to list available Liveboards to link to LiveboardEmbed display page + +import {createConfiguration, ServerConfiguration, ThoughtSpotRestApi} from "@thoughtspot/rest-api-sdk"; + +import {constants} from "@/lib/constants"; +---- + +Note the `"use client";` at the very beginning of the code. This tells React that the REST API calls should be coming from the user's browser, rather than made on the server side. React doesn't explicitly separate frontend and backend layers the way many traditional web application platforms did, so you have to be intentional when you do have a preference. + +=== Initialize the SDK + +The REST API SDK uses an object returned by the imported `createConfiguration()` function to define the attributes necessary to authenticate for a particular ThoughtSpot instance. `createConfiguration()` takes an object with a `baseServer` property that is set to a `ServerConfiguration` option. + +For the `ServerConfiguration` to use the ThoughtSpot session in the browser, rather than request a bearer token, it should be created as shown here: + +`new ServerConfiguration(constants.tsURL, {})` + +For examples of different authentication methods, see the link:https://developers.thoughtspot.com/docs/rest-api-sdk-typescript#_setup_and_usage[REST API SDK documentation]. + +Individual endpoints are called by instantiating a `ThoughtSpotRestApi` object with the `ServerConfiguration` object, and then calling methods on the `ThoughtSpotRestApi` object. + +[source,typescript] +---- + // API configuration using no auth. + const config = createConfiguration({ + baseServer: new ServerConfiguration(constants.tsURL, {}), + }); + ... + // Use ThoughtSpot REST API SDK + const api = new ThoughtSpotRestApi(config); +---- + +=== Call REST API endpoints within useEffect +The general naming pattern for the REST API SDK follows the exact name in the REST API Playground, in camel case (as opposed to being named after the endpoint URL). For a complete list of endpoint methods, see link:https://developers.thoughtspot.com/docs/rest-api-sdk-typescript#_sdk_reference[REST API SDK Reference]. + +Every endpoint method is an async call, so you will need to use the appropriate patterns with `async` and `return await` when using the calls: + +To deal with the async nature of calling a REST API, particularly one outside of the React app itself, wrap any code that issues a REST API commands inside a React `link:https://react.dev/reference/react/useEffect[useEffect( () =>{ })^]` block: + +[source,typescript] +---- + // Wrapper function to retrieve the current username from around api.getCurrentUserInfo() + const fetchUserName = async () => { + const userInfo = await api.getCurrentUserInfo(); + console.log('user === ' + userInfo.name); + setAuthorName(userInfo.name); + } + + useEffect(() => { + // Run necessary REST API calls before rendering the menu + fetchUserName().then(); + ... + } +---- + +For endpoints that accept request parameters body, declare a `const` and set it to the JSON body exactly as copied from the REST API Playground. To change the arguments of the request body, you can add in optional logic to tie it in to the UI state of the React app: + +[source,typescript] +---- +// Define the options for the metadata/search call +const metadataOptions = { + record_size: -1, + include_headers: true, + metadata: [ + { + "type": "LIVEBOARD" + } + ] +} + +if (showMyItems) { + metadataOptions['created_by_user_identifiers'] = [authorName]; +} +... +const fetchFilteredData = await api.searchMetadata(metadataOptions); +---- + +Once the results come back, it is best to store them to a state variable: + +[source,typescript] +---- +const [showMyItems, setShowMyItems] = useState(false); +... +useEffect(() => { + ... + fetchFilteredData().then(); // Call the async function +}, [showMyItems]); +---- + +== Render the component +The visible UI of the page, including the tabular menu, is outside the `useEffect` function. It is created by returning JSX, including some logical operators to vary what displays depending on the current UI state. + +=== My Items filter checkbox +At the top of the page, there is an input of `type="checkbox"`, which determines if the REST API results are filtered only to items created by the current user. + +[,tsx] +---- +// Return the actual page after the API response has been retrieved +return ( +... + {/* Checkbox for My Items filter */} + +... +---- + +If you are unfamiliar with React, JSX format looks like HTML but has a syntax for variables that uses curly braces. + +Setting `checked={showMyItems}` within the `` tag sets the value to match the current boolean `showMyItems` state variable set at the start of the page. Then, the `onChange` function calls `setShowMyItems()` to update the state variable when the box is selected or cleared. + +The `setShowMyItems()` function also triggers any other part of the component that listens for state changes to update. + +The `useEffect()` function takes an array of state variables to listen to as its second argument at the very end of the block: + +[source,typescript] +---- +const [showMyItems, setShowMyItems] = useState(false); +... +useEffect(() => { + ... + fetchFilteredData().then(); // Call the async function +}, [showMyItems]); +---- + +The result is that a change triggered by the checkbox UI state causes the REST API endpoint to be called again, updating the `metadataData` state variable, before the menu table component is re-rendered with the new API response. + +Note that this is a very simple implementation and does call the ThoughtSpot REST API on every change of the UI. ThoughtSpot's REST API is very efficient at answering the `/metadata/search` request. However, you can implement mechanisms within the React page to cache the results to reduce the number of API calls. + +=== Table of items and descriptions +The menu itself is a basic HTML table with two columns. + +The first column displays the Liveboard names as links to the `/dashboard/[dashboardId]` page, and the second column is the text description property. + +The code uses JSX variables and logical operators to display a `No dashboards found` message instead of the table, when the length of the response set from the REST API is found to be 0. + +If not, it uses the `{metadataData.map((item) => ()` syntax to go through every item in the response, making the properties of the item available as the `item` variable for use within the components. + +[,tsx] +---- +{metadataData && metadataData.length > 0 ? ( + +
+ + + + + + + + + {/* Build each row of the menu */} + {metadataData.map((item) => ( + + + {/* Add description from metadata to the second column */} + + + ))} + +
NameDescription
+ {/* Build the Link to the /dashboard/[dashboardId] routes */} + + {item.metadata_name} + + + {item.metadata_header.description || ''} +
+
+) : ( +

No dashboards found

+)} +---- + +The most important aspect is building the `Link` component with the route to the component display page built in the previous lesson, and the `item.metadata_id` property as the end of the URL. + +[source,typescript] +---- + + {item.metadata_name} + +---- + +When this page renders, there is now a dynamic menu to get to any Liveboard the user has access to, with the ability to filter to easily filter to own created content. + +== Spotter embed pages and menu +The example app contains an equivalent menu and component display page for Spotter content, under the link:https://github.com/thoughtspot/embed-example-react-app/tree/main/src/app/datachat[/app/datachat/^] subdirectory. + +Spotter conversations are started against Models (formerly Worksheets), so the set of UI components and filters differs within the link:https://github.com/thoughtspot/embed-example-react-app/blob/main/src/app/datachat/page.tsx[menu page^]. + +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 new file mode 100644 index 000000000..15b0905ae --- /dev/null +++ b/modules/tutorials/pages/react-components/react-components_lesson-04.adoc @@ -0,0 +1,49 @@ += 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 + +The ThoughtSpot Visual Embed SDK provides a set of Embed events that can be listened to and handled via callback functions. + +== Event listener props +Every link:https://developers.thoughtspot.com/docs/Enumeration_EmbedEvent[EmbedEvent^] has an equivalent *prop* in the React components where the event is applicable, with the naming pattern `on{EmbedEvent}`: + +[,tsx] +---- +// Required for referencing the LiveboardEmbed that is created in other code +const embedRef = useEmbedRef(); + +return +---- + +== Use embedRef to update ThoughtSpot component + +By setting the *ref* prop to the *embedRef* const declared earlier within the page, functions can reference the embed component within their code to trigger a `HostEvent` without causing the React page to attempt to re-render the embedded ThoughtSpot component. + +For example, the handler function `afterEmbedRendered` assigned to `onLiveboardRendered` above might look like: + +[source,typescript] +---- +const afterEmbedRendered = () => { + embedRef.current.trigger(HostEvent.SetVisibleVizs, [ + "3f84d633-e325-44b2-be25-c6650e5a49cf", + "28b73b4a-1341-4535-ab71-f76b6fe7bf92", + ]); +}; +---- + +The `embedRef.current` syntax allows for issuing the `.trigger()` method of calling in a link:https://developers.thoughtspot.com/docs/Enumeration_HostEvent[HostEvent] in when the `EmbedEvent` has fired off. + +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/style-customization/style-customization_intro.adoc b/modules/tutorials/pages/style-customization/style-customization_intro.adoc deleted file mode 100644 index 0d5e66093..000000000 --- a/modules/tutorials/pages/style-customization/style-customization_intro.adoc +++ /dev/null @@ -1,5 +0,0 @@ -= Visual Embed SDK Style Customization Tutorial -:page-pageid: style-customization_intro -:description: This is a self-guided course on style customization of the Visual Embed SDK components -:toc: true -:toclevels: 2 diff --git a/static/doc-images/images/spotter-icon-customization.png b/static/doc-images/images/spotter-icon-customization.png new file mode 100644 index 000000000..9ae918a51 Binary files /dev/null and b/static/doc-images/images/spotter-icon-customization.png differ diff --git a/static/doc-images/images/spotter-icon.png b/static/doc-images/images/spotter-icon.png new file mode 100644 index 000000000..c8a00cb54 Binary files /dev/null and b/static/doc-images/images/spotter-icon.png differ diff --git a/static/doc-images/images/tutorials/react-components/next-js-app-structure.png b/static/doc-images/images/tutorials/react-components/next-js-app-structure.png new file mode 100644 index 000000000..da37c42f2 Binary files /dev/null and b/static/doc-images/images/tutorials/react-components/next-js-app-structure.png differ diff --git a/static/doc-images/images/tutorials/react-components/readme.txt b/static/doc-images/images/tutorials/react-components/readme.txt new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/static/doc-images/images/tutorials/react-components/readme.txt @@ -0,0 +1 @@ +