# Callback custom action workflow

> For the complete documentation index, see [llms.txt](https://developers.thoughtspot.com/docs/llms.txt)

Source: https://developers.thoughtspot.com/docs/push-data

# Callback custom action workflow

ThoughtSpot allows you to create custom actions to push data to a third-party application from the ThoughtSpot application context. You can configure this custom menu item to initiate an action or a callback to the parent application. For example, if you have embedded the ThoughtSpot search functionality in your application, you can add an action to send the data obtained from a search query to an external application or a workflow.

> **NOTE:** The data is available to the external application in the JavaScript Object Notation (JSON) format. You must parse the JSON data to get the values you need using JavaScript in the receiving application.

## Push data using a callback custom action

To push data to an external app through a callback custom action, follow these steps:

-   [Add a callback custom action in ThoughtSpot]({{navprefix}}/{{push-data-to-external-app}}#add-callback)
    
-   [Register the custom action]({{navprefix}}/{{push-data-to-external-app}}#register-callback)
    
-   [Initiate a callback]({{navprefix}}/{{push-data-to-external-app}}#callback-initiate)
    
-   [Define classes and functions to handle custom action data]({{navprefix}}/{{push-data-to-external-app}}#handle-data)
    

### Add a callback custom action in the ThoughtSpot

You can [Create a callback custom action]({{navprefix}}/{{custom-actions-callback}}) in the ThoughtSpot Developer portal (**Develop** > **Customization** > **Custom actions** > **Create action**) or using the [/tspublic/v1/admin/embed/actions]({{navprefix}}/{{admin-api}}#create-custom-action) REST API.

### Register the callback through the Visual Embed SDK

Register the callback using the Visual Embed SDK. This configuration is required to trigger an event when the custom action is initiated.

The following example shows how to call `showData` when a custom action is received.

```Javascript
const searchEmbed = new SearchEmbed('#embed', {
    frameParams: {},
});
embed
    .on(EmbedEvent.CustomAction, (payload) => {
        showData(payload);
    })
    .render();
```

### Initiate the callback action

Define the function and classes to handle the data that you want to send as a payload when a custom action is triggered. This example shows how to handle the `show-data` callback custom action and the details of its data:

```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');
        dataContentElement.innerHTML = html;
        const dataElement = document.getElementById('show-data');
        dataElement.style.display = 'block';
    } else {
        console.log(`Got unknown custom actions ${data.id}`);
    }
}
```

ThoughtSpot visualization objects can include several rows of records. For callback custom action payloads, the Visual Embed SDK supports retrieving data in batches.

The following example shows how to handle large datasets in custom action payloads:

```Javascript
const data = payload.data;
if (data.id === 'show-data') {
    const fetchAnswerData = await payload.answerService.fetchData(1, 5); //where the first integer is the offset value and second integer is batchsize
    console.log('fetchAnswerData:::', fetchAnswerData);
}
```

### Define functions and classes to handle custom action data

The following code snippet shows the sample classes and functions for handling custom action data. For an up-to-date version of the code sample, see [dataclasses.js](https://github.com/thoughtspot/ts_everywhere_resources/blob/master/apis/dataclasses.js).

```Javascript
const zip = (arrays) => {
    // combines and inverts arrays, so a = [1, 2, 3], b = [4, 5, 6] becomes [[1,4], [2,5], [3,6]]
    return arrays[0].map(function(_, i) {
        return arrays.map(function(array) {
            return array[i]
        })
    });
}
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.
    }
    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.
            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);
        }
        let dataSet;
        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
        }
        return actionData
    }
    getDataAsTable() {
        // returns the data as a table.  The columns will be in the same order as the column headers.
        const arrays = []
        for (const cname of this._columnNames) {
            arrays.push(this._data[cname])
        }
        return zip(arrays); // returns a two dimensional data array
    }
}
const actionDataToHTML = (actionData) => {
    // Converts an ActionData data to an HTML table.
    let table = '<table class="tabular-data">';
    // Add a header
    table += '<tr>';
    for (const columnName of actionData._columnNames) {
        table += `<th class="tabular-data-th">${columnName}</th>`;
    }
    table += '</tr>';
    const data = actionData.getDataAsTable();
    for (let rnbr = 0; rnbr < actionData.nbrRows; rnbr++) {
        table += '<tr>';
        for (let cnbr = 0; cnbr < actionData.nbrColumns; cnbr++) {
            table += `<td class="tabular-data">${data[rnbr][cnbr]}</td>`;
        }
        table += '</tr>';
    }
    table += '</table>';
    return table;
}
export {
    ActionData,
    actionDataToHTML
}
```

For sample response payloads, see [Custom action response payload]({{navprefix}}/{{callback-response-payload}}).