From 24f848a492c7672c85ebe630e905f85a7ba59cbb Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Thu, 9 Jul 2026 08:21:22 +0530 Subject: [PATCH] SCAL-318342 --- modules/ROOT/pages/common/nav-embedding.adoc | 1 + .../pages/common/nav-in-product-help.adoc | 1 + .../custom-action-callback-data-classes.adoc | 292 ++++++++++++++++++ .../ROOT/pages/custom-actions-callback.adoc | 232 ++++++-------- 4 files changed, 382 insertions(+), 144 deletions(-) create mode 100644 modules/ROOT/pages/custom-action-callback-data-classes.adoc diff --git a/modules/ROOT/pages/common/nav-embedding.adoc b/modules/ROOT/pages/common/nav-embedding.adoc index 4f5f8a246..68aa1259a 100644 --- a/modules/ROOT/pages/common/nav-embedding.adoc +++ b/modules/ROOT/pages/common/nav-embedding.adoc @@ -99,6 +99,7 @@ Customize and integrate ** link:{{navprefix}}/customize-actions[Custom actions through the UI] *** link:{{navprefix}}/custom-action-url[URL actions] *** link:{{navprefix}}/custom-action-callback[Callback actions] +**** link:{{navprefix}}/custom-action-callback-data-classes[Parse callback payloads with tse-data-classes] *** link:{{navprefix}}/edit-custom-action[Set the position of a custom action] *** link:{{navprefix}}/add-action-viz[Add a local action to a visualization] *** link:{{navprefix}}/add-action-worksheet[Add a local action to a model] diff --git a/modules/ROOT/pages/common/nav-in-product-help.adoc b/modules/ROOT/pages/common/nav-in-product-help.adoc index cf7312ff9..8b4ad956b 100644 --- a/modules/ROOT/pages/common/nav-in-product-help.adoc +++ b/modules/ROOT/pages/common/nav-in-product-help.adoc @@ -118,6 +118,7 @@ Customize and integrate ** link:{{navprefix}}/customize-actions[Custom actions through the UI] *** link:{{navprefix}}/custom-action-url[URL actions] *** link:{{navprefix}}/custom-action-callback[Callback actions] +**** link:{{navprefix}}/custom-action-callback-data-classes[Parse callback payloads with tse-data-classes] *** link:{{navprefix}}/edit-custom-action[Set the position of a custom action] *** link:{{navprefix}}/add-action-viz[Add a local action to a visualization] *** link:{{navprefix}}/add-action-worksheet[Add a local action to a model] diff --git a/modules/ROOT/pages/custom-action-callback-data-classes.adoc b/modules/ROOT/pages/custom-action-callback-data-classes.adoc new file mode 100644 index 000000000..b7dbeeccc --- /dev/null +++ b/modules/ROOT/pages/custom-action-callback-data-classes.adoc @@ -0,0 +1,292 @@ += Parse callback payloads with tse-data-classes +:toc: true + +:page-title: Parse ThoughtSpot callback payloads with the tse-data-classes package +:page-pageid: custom-action-callback-data-classes +:page-description: Use the tse-data-classes npm package to parse and access JSON payloads returned by ThoughtSpot callback custom actions. + +The `tse-data-classes` npm package provides TypeScript classes that simplify parsing and accessing the JSON payload returned by callback custom actions. Use this package instead of writing custom payload parsers from scratch. + +[NOTE] +==== +This page covers the `tse-data-classes` package approach for TypeScript projects. For vanilla JavaScript examples and general callback setup instructions, see xref:custom-actions-callback.adoc[Callback custom actions]. +==== + +[div boxDiv boxFullWidth] +-- ++++
Feature highlights
+++ + +* Install `tse-data-classes` from npm — no manual class implementation required. +* Five classes cover all callback action targets: Search, Answer, Liveboard visualization, REST API v2 search data, and REST API v2 answer data. +* All classes extend `TabularData` and share the same properties and methods, including `getDataAsTable()`. +* TypeScript type interfaces are included for all payload shapes. +-- + +[IMPORTANT] +==== +The `tse-data-classes` package was last verified against ThoughtSpot 10.15. Review the following compatibility notes before using these classes in your implementation: + +* *Liveboard new experience*: The Liveboard callback payload structure changed with the new Liveboard experience (SDK v1.19.0, enabled by default on all instances). Verify that `LiveboardActionData.createFromJSON()` correctly parses your payload before deploying. See the link:https://developers.thoughtspot.com/docs/api-changelog[SDK changelog] for details. +* *Spotter custom actions*: When a callback custom action targets a Spotter interface using `CustomActionTarget.SPOTTER`, the payload does not contain tabular data. Do not pass a Spotter action payload to any `tse-data-classes` class — doing so produces undefined behavior. Check `payload.data.id` before calling `createFromJSON()`. +* *Generic event listeners on AppEmbed*: If you register `EmbedEvent.CustomAction` on a full application embed (`AppEmbed`), the listener fires for all custom action targets including Spotter. Add an action ID check before passing the payload to a data class. +==== + +== Install the package + +[source,bash] +---- +npm install tse-data-classes +---- + +Import the classes and type interfaces you need: + +[source,javascript] +---- +import { ActionData, ActionDataType } from 'tse-data-classes'; +---- + +[#available-classes] +== Available classes + +The following classes are available in the package. All classes extend `TabularData` and share the same properties and methods. + +[width="100%",cols="2,4,2"] +|=== +|Class |Use case |Does NOT apply to + +|`ActionData` +|Callback action triggered from the main menu or primary action button on a Search result or saved Answer. Also handles `EmbedEvent.Data` events. +|Liveboard visualizations; context menu actions + +|`ContextActionData` +|Callback action triggered from a context menu (right-click) on a Search result or saved Answer. +|Liveboard visualizations; main menu actions + +|`LiveboardActionData` +|Callback action triggered from the **More** options menu (⋮) of a Liveboard visualization. +|Search results; saved Answers; context menus + +|`SearchData` +|Data returned by the REST API v2 `/searchdata` endpoint. +|Embedded callback events + +|`AnswerData` +|Data returned by the REST API v2 `/fetch-answer-data` endpoint. +|Embedded callback events +|=== + +[#tabular-data] +== TabularData properties and methods + +All classes inherit the following properties and methods from the `TabularData` base class: + +[width="100%",cols="2,1,4"] +|=== +|Property or method |Type |Description + +|`columnNames` +|`string[]` +|Returns the names of all columns in the payload, in the order they appear. + +|`nbrColumns` +|`number` +|Returns the total number of columns. + +|`nbrRows` +|`number` +|Returns the total number of data rows. + +|`originalData` +|`object` +|Returns the raw, unparsed JSON payload object as received from the callback event. + +|`getDataAsTable(columnNames?: string[])` +|`any[][]` +|Returns data as a two-dimensional row-oriented array. Pass an optional array of column name strings to extract specific columns. If omitted, all columns are returned. Column name matching is case-sensitive. +|=== + +=== getDataAsTable() + +[source,javascript] +---- +getDataAsTable(columnNames?: string[]): any[][] +---- + +[cols="1,3"] +|=== +|Parameter |Description + +|`columnNames` +|Optional. An array of column name strings to extract. Column name matching is case-sensitive. If omitted or empty, all columns are returned. +|=== + +The method returns an array of rows, where each row is an array of cell values in the order of the requested columns: + +[source,javascript] +---- +// Returns all columns as a 2D row array +const allData = actionData.getDataAsTable(); + +// Returns specific columns only +const subset = actionData.getDataAsTable(['Product', 'Quantity']); +// Example output: [ ['Widget A', 120], ['Widget B', 85] ] +---- + +[#type-interfaces] +== TypeScript type interfaces + +Use the following interfaces to type your callback payload handler: + +[width="100%",cols="2,4"] +|=== +|Interface |Describes + +|`ActionDataType` +|Payload shape for `EmbedEvent.CustomAction` on a Search result or saved Answer (main menu action). + +|`ContextActionDataType` +|Payload shape for `EmbedEvent.CustomAction` triggered from a context menu. + +|`LiveboardDataType` +|Data structure returned by the REST API v2 Liveboard data endpoint. + +|`SearchDataType` +|Data structure returned by the REST API v2 `/searchdata` endpoint. + +|`AnswerDataType` +|Data structure returned by the REST API v2 `/fetch-answer-data` endpoint. +|=== + +[#parse-answer] +== Parse Answer and Search result payloads + +Use the `ActionData` class for callback actions triggered from the main menu or primary action button on a Search result or saved Answer. + +[source,javascript] +---- +import { ActionData, ActionDataType } from 'tse-data-classes'; + +searchEmbed.on(EmbedEvent.CustomAction, (payload) => { + // TODO: verify with engineering — confirm whether createFromJSON() expects + // the full payload object or payload.data for tse-data-classes v1.x. + if (payload.data.id === 'show-data') { + const actionData = ActionData.createFromJSON(payload as ActionDataType); + + // Access column names + console.log('Columns:', actionData.columnNames); + + // Retrieve all data as a 2D row array + const table = actionData.getDataAsTable(); + console.log('Data rows:', table); + + // Retrieve specific columns only + const subset = actionData.getDataAsTable(['Product', 'Revenue']); + console.log('Subset:', subset); + } +}); +---- + +[#parse-liveboard] +== Parse Liveboard visualization payloads + +Use the `LiveboardActionData` class for callback actions triggered from the **More** options menu (⋮) of a Liveboard visualization. + +[source,javascript] +---- +import { LiveboardActionData } from 'tse-data-classes'; + +const embed = new LiveboardEmbed('#embed', { + liveboardId: 'e40c0727-01e6-49db-bb2f-5aa19661477b', + vizId: '8d2e93ad-cae8-4c8e-a364-e7966a69a41e', +}); + +embed.on(EmbedEvent.CustomAction, (payload) => { + if (payload.data.id === 'show-data') { + const liveboardActionData = LiveboardActionData.createFromJSON(payload); + + // Access column names + console.log('Columns:', liveboardActionData.columnNames); + + // Get all rows as a 2D array + const table = liveboardActionData.getDataAsTable(); + console.log('Data rows:', table); + + const dataElement = document.getElementById('show-data'); + dataElement.style.display = 'block'; + } +}); + +embed.render(); +---- + +NOTE: `LiveboardActionData` only applies to callback actions in the **More** options menu (⋮) of a Liveboard visualization. For context menu actions, use `ContextActionData`. For Search and Answer main-menu actions, use `ActionData`. + +[#parse-context] +== Parse context menu action payloads + +Use the `ContextActionData` class for callback actions triggered from the context menu (right-click) on a Search result or saved Answer. + +[source,javascript] +---- +import { ContextActionData, ContextActionDataType } from 'tse-data-classes'; + +searchEmbed.on(EmbedEvent.CustomAction, (payload) => { + if (payload.data.id === 'context-action') { + const contextData = ContextActionData.createFromJSON( + payload.data as ContextActionDataType + ); + + // Iterate selected attributes (dimensions) and measures + contextData.originalData.data.contextMenuPoints.selectedPoints.forEach((point) => { + point.selectedAttributes.forEach((attr) => { + console.log(`${attr.column.name}: ${attr.value}`); + }); + point.selectedMeasures.forEach((measure) => { + console.log(`${measure.column.name}: ${measure.value}`); + }); + }); + } +}); +---- + +[#parse-rest-api] +== Parse REST API v2 data responses + +Use `SearchData` and `AnswerData` to parse responses from the ThoughtSpot REST API v2 data endpoints. These classes use the same `TabularData` properties and methods as the embedded callback classes. + +=== SearchData + +[source,javascript] +---- +import { SearchData, SearchDataType } from 'tse-data-classes'; + +// response is the JSON body from POST /api/v2/searchdata +const searchData = SearchData.createFromJSON(response as SearchDataType); + +console.log('Columns:', searchData.columnNames); +console.log('Row count:', searchData.nbrRows); + +// Get all data as a 2D row array +const table = searchData.getDataAsTable(); +---- + +=== AnswerData + +[source,javascript] +---- +import { AnswerData, AnswerDataType } from 'tse-data-classes'; + +// response is the JSON body from POST /api/v2/metadata/answer/data +const answerData = AnswerData.createFromJSON(response as AnswerDataType); + +// Extract specific columns +const filtered = answerData.getDataAsTable(['Product', 'Sales']); +---- + +== Additional resources + +* link:https://www.npmjs.com/package/tse-data-classes[tse-data-classes on npm, window=_blank] +* link:https://github.com/thoughtspot/tse-advanced-step-by-step-source[tse-advanced-step-by-step-source on GitHub, window=_blank] +* xref:custom-actions-callback.adoc[Callback custom actions] +* xref:callback-response-payload.adoc[Callback JSON response payloads] +* link:https://github.com/thoughtspot/ts_everywhere_resources/blob/master/apis/dataclasses.js[dataclasses.js — legacy reference, window=_blank] \ No newline at end of file diff --git a/modules/ROOT/pages/custom-actions-callback.adoc b/modules/ROOT/pages/custom-actions-callback.adoc index 3a7df7ccd..b3e244032 100644 --- a/modules/ROOT/pages/custom-actions-callback.adoc +++ b/modules/ROOT/pages/custom-actions-callback.adoc @@ -5,8 +5,7 @@ :page-pageid: custom-action-callback :page-description: Set up a callback function to the host application and trigger a response payload from the embedded ThoughtSpot component. -Callback custom actions allow you to get data payloads from an embedded ThoughtSpot object and initiate a callback to the host application. For example, if you have embedded a ThoughtSpot Liveboard in your application, you can add a callback action to the Liveboard menu to get a JSON response payload from the Liveboard visualization and initiate a callback to your app. - +Callback custom actions allow you to get data payloads from an embedded ThoughtSpot object and start a callback to the host application. For example, if you have embedded a ThoughtSpot Liveboard in your application, you can add a callback action to the Liveboard menu to get a JSON response payload from the Liveboard visualization and start a callback to your app. [div boxDiv boxFullWidth] -- @@ -16,17 +15,22 @@ Callback custom actions allow you to get data payloads from an embedded ThoughtS * Users with developer or admin privileges can create a callback custom action in the Developer portal. * Developers can set a callback action as a global or local action. * Users with edit permissions to a Model or visualization can add a local callback action to a visualization or Answer of their choice. -* Developers must register the callback in the Visual Embed SDK and define data classes and functions to parse and process the JSON data payload retrieved from the callback event. +* Developers must register the callback in the Visual Embed SDK and define functions to parse and process the JSON data payload retrieved from the callback event. -- == Get started +[NOTE] +==== +The code examples in this section show vanilla JavaScript patterns for handling callback payloads. If you are using TypeScript, the link:https://www.npmjs.com/package/tse-data-classes[tse-data-classes] npm package provides ready-made classes that simplify payload parsing. See xref:custom-action-callback-data-classes.adoc[Parse callback payloads with tse-data-classes]. +==== + To set up a callback custom action workflow, complete the following steps: * xref:custom-actions-callback.adoc#add-callback[Create a callback custom action] * xref:custom-actions-callback.adoc#register-callback[Register the custom action] -* xref:custom-actions-callback.adoc#handle-data[Define classes and functions to parse JSON and handle data] -* xref:custom-actions-callback.adoc#callback-initiate[Initiate a callback and verify your implementation] +* xref:custom-actions-callback.adoc#handle-data[Parse the JSON response payload] +* xref:custom-actions-callback.adoc#callback-start[Start the callback and verify your implementation] [#add-callback] == Create a callback custom action @@ -38,19 +42,14 @@ Before you begin, make sure you have the developer privileges to add a custom ac . Add a label for the custom action. For example, __Send Data__. . Select the *Callback* option. . Note the callback action ID. - + The callback ID is used as a unique reference in the Visual Embed SDK to handle the callback event. You can also use this ID to disable, show, or hide the callback action in the embedded app. - + By default, custom actions are added to all Liveboard visualizations and saved Answers and set as Global. If you want to add this action only to specific visualizations, clear the *On by default on all visualizations* checkbox. - -. To restrict action availability to specific user groups, click *Show advanced availability settings*, and select the groups. - +. To restrict action availability to specific user groups, click *Show advanced availability settings*, and select the groups. . Click *Create action*. + The custom action is added to the *Actions* dashboard in the Developer portal. - . To view and verify the custom action you just created, navigate to a visualization page. include::{path}/global-local-action.adoc[] @@ -60,122 +59,96 @@ include::{path}/global-local-action.adoc[] After a callback custom action is added in ThoughtSpot, add an event handler to listen to the callback event and trigger a data payload as a response when a user clicks the callback action in the UI. -In this example, the data payload from the custom action response is logged in the console. +In the following examples, the data payload from the custom action response is logged in the console. -**Example code for Answer payloads** +**Example: Answer payload** -[source,Javascript] +[source,javascript] ---- -searchEmbed.on(EmbedEvent.CustomAction, payload => { +searchEmbed.on(EmbedEvent.CustomAction, (payload) => { const data = payload.data; if (data.id === 'show-data') { console.log('Custom Action event:', data.embedAnswerData); } -}) +}); ---- -**Example code to get underlying data using `AnswerService` class** +**Example: Get underlying data using the AnswerService class** -Use the link:https://developers.thoughtspot.com/docs/Class_AnswerService[`AnswerService`] class to run GraphQL queries in the context of the Answer on which the custom action is triggered. +Use the link:https://developers.thoughtspot.com/docs/Class_AnswerService[`AnswerService`] class to run GraphQL queries in the context of the Answer on which the custom action is triggered. -[source,Javascript] +[source,javascript] ---- - searchEmbed.on(EmbedEvent.CustomAction, async e => { +searchEmbed.on(EmbedEvent.CustomAction, async (e) => { const underlying = await e.answerService.getUnderlyingDataForPoint([ - 'col name 1' + 'col name 1' ]); const data = await underlying.fetchData(0, 100); - }) +}); ---- -**Example code for Liveboard payload (Classic experience mode)** +**Example: Liveboard payload (classic experience mode)** -[source,Javascript] +[source,javascript] ---- -liveboardEmbed.on(EmbedEvent.CustomAction, payload => { +liveboardEmbed.on(EmbedEvent.CustomAction, (payload) => { if (payload.id === 'show-data') { console.log('Custom Action event:', payload.data); } -}) +}); ---- -**Example code for Liveboard data payload (New experience mode)** +**Example: Liveboard data payload (new experience mode)** -[source,Javascript] +[source,javascript] ---- -liveboardEmbed.on(EmbedEvent.CustomAction, payload => { +liveboardEmbed.on(EmbedEvent.CustomAction, (payload) => { const customActionId = 'show-data'; if (payload.data.id === customActionId) { console.log('Custom Action event:', payload.data); } -}) +}); ---- -**Example code to fetch large datasets in batches** +**Example: Fetch large datasets in batches** -[source, Javascript] +[source,javascript] ---- const data = payload.data; if (data.id === 'show-data') { + // First integer is the offset; second integer is the batch size. const fetchAnswerData = await payload.answerService.fetchData(1, 5); - //where the first integer is the offset value and the second integer is batchsize console.log('fetchAnswerData:::', fetchAnswerData); } ---- == Process large datasets in batches +// TODO: verify with engineering — review callback-payload.adoc for consistency with the tse-data-classes approach before publishing. include::{path}/callback-payload.adoc[] [#handle-data] -== Parse JSON response payload - -The callback actions can return JSON payloads that are complex and need to be parsed before using it for application needs. The format of the JSON response payload can vary based on the type of the embedded object and the placement of the custom action in the menu. For example, the format of the data payload triggered by an action on a Liveboard visualization is different from the data retrieved for an Answer. When defining functions in your code to parse and handle data, make sure you use the correct classes. +== Parse the JSON response payload -=== Define functions and classes to handle Liveboard data +Callback actions return a JSON payload that must be parsed before your application can use the data. The payload format varies based on the type of embedded object and the placement of the custom action in the menu. For example, the payload triggered by a Liveboard visualization action has a different structure from the payload returned for an Answer. -The following example shows how to get data from a callback action triggered on a Liveboard visualization: +For a full reference of payload structures, see xref:callback-response-payload.adoc[Callback JSON response payloads]. -[source,JavaScript] ----- -const onCustomAction = () => { - const embed = new LiveboardEmbed("#embed", { - liveboardId: "e40c0727-01e6-49db-bb2f-5aa19661477b", - vizId: "8d2e93ad-cae8-4c8e-a364-e7966a69a41e", - }); - embed.on(EmbedEvent.CustomAction, payload => { - if (payload.id === "show-data" || "payload.data.id === show-data") { - showData(payload) - } - }) - .render(); - const showData = (payload) => { - const liveboardActionData = LiveboardActionData.createFromJSON(payload); - const dataElement = document.getElementById('show-data'); - dataElement.style.display = 'block'; - } ----- +If you are using TypeScript, see xref:custom-action-callback-data-classes.adoc[Parse callback payloads with tse-data-classes] for a simpler approach using the `tse-data-classes` package. -The format of the data payload for Liveboard visualization varies if the callback action is triggered from a Liveboard in the new experience mode. To view the payload structure, refer to the examples on xref:callback-response-payload.adoc[Custom action response payload] page. +=== Parse Liveboard visualization data -The following code sample shows sample classes and functions for parsing JSON data from a Liveboard. This example assumes that the callback action is placed in the **More** options menu of the Liveboard visualization. +The following example shows how to parse a callback payload triggered from a Liveboard visualization. The `LiveboardActionData` class handles both the classic and new Liveboard experience payload formats. -[source,JavaScript] +[source,javascript] ---- /** - * This class handles data from Liveboard visualizations if the callback action is set as - * a More menu or primary action. - * It does not work for Search or saved Answer data payloads or callback actions in the context menus. + * Handles data from Liveboard visualizations for More menu or primary actions. + * Does not apply to Search, saved Answer, or context menu callback actions. */ class LiveboardActionData { - /** - * Creates a new LiveboardContextActionData from the payload. - * @param jsonData A string from payload.data - * @returns {LiveboardContextActionData} - */ static createFromJSON(jsonData) { let isV1 = true; - // Handle data structure differences between Liveboards operating in the classic and new experience modes. if (typeof jsonData.data === 'string' || jsonData.data instanceof String) { jsonData = JSON.parse(jsonData.data); isV1 = true; @@ -189,57 +162,51 @@ class LiveboardActionData { const data = []; if (isV1) { - const reportBookData = getValues(jsonData.reportBookData)[0]; // assume there's only one. - const vizData = getValues(reportBookData.vizData)[0]; // assume there's only one. - - // Get the column meta information. + // Classic experience mode: payload uses reportBookData structure. + const reportBookData = getValues(jsonData.reportBookData)[0]; + const vizData = getValues(reportBookData.vizData)[0]; const columns = vizData.dataSets.PINBOARD_VIZ.columns; - const nbrCols = columns.length; - for (let colCnt = 0; colCnt < nbrCols; colCnt += 1) { + for (let colCnt = 0; colCnt < columns.length; colCnt += 1) { columnNames.push(columns[colCnt].column.name); } - // can come in two flavors, so need to get the right data - const dataSet = (Array.isArray(vizData.dataSets.PINBOARD_VIZ.data)) ? - vizData.dataSets.PINBOARD_VIZ.data[0].columnDataLite : - vizData.dataSets.PINBOARD_VIZ.data.columnDataLite; - + const dataSet = (Array.isArray(vizData.dataSets.PINBOARD_VIZ.data)) + ? vizData.dataSets.PINBOARD_VIZ.data[0].columnDataLite + : vizData.dataSets.PINBOARD_VIZ.data.columnDataLite; for (let cnt = 0; cnt < columnNames.length; cnt++) { - data.push(dataSet[cnt].dataValue); // should be an array of columns values. + data.push(dataSet[cnt].dataValue); } - } else { // is v2 + } else { + // New experience mode: payload uses embedAnswerData structure. const columns = jsonData.embedAnswerData.columns; - const nbrCols = columns.length; - for (let colCnt = 0; colCnt < nbrCols; colCnt++) { + for (let colCnt = 0; colCnt < columns.length; colCnt++) { columnNames.push(columns[colCnt].column.name); } - for (let colCnt = 0; colCnt < nbrCols; colCnt++) { - // The data is always under 0 for what we want. - data.push(jsonData.embedAnswerData.data[0].columnDataLite[colCnt].dataValue); + for (let colCnt = 0; colCnt < columns.length; colCnt++) { + data.push( + jsonData.embedAnswerData.data[0].columnDataLite[colCnt].dataValue + ); } } liveboardActionData.columnNames = columnNames; } catch (error) { console.error(`Error creating Liveboard action data: ${error}`); - console.error(jsonData); } return liveboardActionData; } } ---- -The above example uses additional classes and functions to parse and get data in the tabular format, the number of columns, rows, and column names. These classes are defined in the code sample in `dataclasses.js` on link:https://github.com/thoughtspot/ts_everywhere_resources/blob/master/apis/dataclasses.js[ThoughtSpot Embedded Resources GitHub repository, window="_blank"]. You can also find sample classes and functions to parse JSON payload from context menu actions in `dataclasses.js`. - +NOTE: This class handles callback actions placed in the **More** options menu (⋮) of a Liveboard visualization. For context menu actions, use a `ContextActionData` class pattern. For Search and Answer main-menu actions, use an `ActionData` class pattern. -=== Define functions and classes to handle Answer data +=== Parse Answer data -The following example shows the code sample to get Answer data from the `show-data` callback action and sample classes and functions to parse the JSON response payload. +The following example shows how to parse a callback payload from a Search result or saved Answer, and render the data as an HTML table. -[source,Javascript] +[source,javascript] ---- const showData = (payload) => { const data = payload.data; if (data.id === 'show-data') { - // Display the data as a table. const actionData = ActionData.createFromJSON(payload); const html = actionDataToHTML(actionData); const dataContentElement = document.getElementById('modal-data-content'); @@ -247,74 +214,57 @@ const showData = (payload) => { const dataElement = document.getElementById('show-data'); dataElement.style.display = 'block'; } else { - console.log(`Got unknown custom actions ${data.id}`); + console.log(`Got unknown custom action: ${data.id}`); } } ----- - -The following example shows the sample classes and functions for handling custom action data: -[source,Javascript] ----- -//This function converts column-based representations of the data to a table for display -const zip = (arrays) => { - return arrays[0].map(function(_, i) { - return arrays.map(function(array) { - return array[i] - }) - }); -} /** - * This class handles data from Search and Answers if the callback action is set as a More menu or primary action. - * It does not work for Liveboard visualizations or callback actions in the context menu. + * Handles data from Search and Answers for More menu or primary callback actions. + * Does not apply to Liveboard visualizations or context menu callback actions. */ class ActionData { - // Wrapper for the data sent when a custom action is triggered. constructor() { - this._columnNames = []; // list of the columns in order. - this._data = {}; // data is stored and indexed by column with the index being column name. + this._columnNames = []; + this._data = {}; } get nbrRows() { - // Returns the number of rows. Assumes all columns are of the same length. - if (this._columnNames && Object.keys(this._data)) { // make sure there is some data. + if (this._columnNames && Object.keys(this._data)) { return this._data[this._columnNames[0]]?.length; } return 0; } get nbrColumns() { - // Returns the number of columns. return this._columnNames.length; } static createFromJSON(jsonData) { - // Creates a new ActionData object from JSON. const actionData = new ActionData(); - // Gets the column names. const nbrCols = jsonData.data.embedAnswerData.columns.length; for (let colCnt = 0; colCnt < nbrCols; colCnt += 1) { - actionData._columnNames.push(jsonData.data.embedAnswerData.columns[colCnt].column.name); + actionData._columnNames.push( + jsonData.data.embedAnswerData.columns[colCnt].column.name + ); } let dataSet; - dataSet = (Array.isArray(jsonData.data.embedAnswerData.data)) ? - jsonData.data.embedAnswerData.data[0].columnDataLite : - jsonData.data.embedAnswerData.data.columnDataLite; + dataSet = (Array.isArray(jsonData.data.embedAnswerData.data)) + ? jsonData.data.embedAnswerData.data[0].columnDataLite + : jsonData.data.embedAnswerData.data.columnDataLite; for (let colCnt = 0; colCnt < actionData.nbrColumns; colCnt++) { - actionData._data[actionData._columnNames[colCnt]] = Array.from(dataSet[colCnt].dataValue); // shallow copy the data + actionData._data[actionData._columnNames[colCnt]] = + Array.from(dataSet[colCnt].dataValue); } - return actionData + return actionData; } getDataAsTable() { - // returns the data as a table. The columns will be in the same order as the column headers. - const arrays = [] + const arrays = []; for (const cname of this._columnNames) { - arrays.push(this._data[cname]) + arrays.push(this._data[cname]); } - return zip(arrays); // returns a two dimensional data array + return arrays[0].map((_, i) => arrays.map((arr) => arr[i])); } } + const actionDataToHTML = (actionData) => { - // Converts an ActionData data to an HTML table. let table = ''; - // Add a header table += ''; for (const columnName of actionData._columnNames) { table += ``; @@ -331,29 +281,23 @@ const actionDataToHTML = (actionData) => { table += '
${columnName}
'; return table; } -export { - ActionData, - actionDataToHTML -} ---- +For additional helper classes and functions, see link:https://github.com/thoughtspot/ts_everywhere_resources/blob/master/apis/dataclasses.js[dataclasses.js, window=_blank] on GitHub. -[#callback-initiate] -== Initiate a callback and test your implementation +[#callback-start] +== Start the callback and test your implementation -To initiate a callback action: +To start a callback action: . Navigate to a Liveboard visualization or saved Answer page. + Custom actions appear as disabled on unsaved charts and tables. If you have generated a chart or table from a new search query, you must save the Answer before associating a custom action. . Click the callback action. -. Verify if the callback action triggers a payload and initiates a callback to the host app. - +. Verify that the callback action triggers a payload and starts a callback to the host application. == Additional resources -* link:https://github.com/thoughtspot/ts_everywhere_resources/blob/master/apis/dataclasses.js[ThoughtSpot Embedded Resources, window=_blank] - -//// -* link:https://github.com/thoughtspot/ts_everywhere_resources/tree/master/example_actions/download_csv[Custom action examples, window=_blank] -//// \ No newline at end of file +* xref:custom-action-callback-data-classes.adoc[Parse callback payloads with tse-data-classes] +* xref:callback-response-payload.adoc[Callback JSON response payloads] +* link:https://github.com/thoughtspot/ts_everywhere_resources/blob/ma \ No newline at end of file