Sample (and extremely simple) Flask app that can be used to test the Preset Embedding experience! Note that this app is solely intended to demonstrate the embedded implementation, and shouldn't be used in a Production environment.
This project uses Python to run a Flask app. We strongly encourage using a Python Virtual environment to install it (this tutorial won't cover this part).
Start by cloning the repository to your local environment. Then, duplicate the .env-example file, and save it as .env in the root folder (the file is automatically git-ignored). This file is responsible for providing the app with your credentials, team and dashboard information. Replace template values in there accordingly (you might need to add quotes if the values have special characters).
Let's take a look on how to fill it:
DISCLAIMER: Your API token and secret are only stored in this local file — this information is not processed or synced anywhere else. It's also possible to run this app without providing your credentials, however you would have to generate the Guest Token on your end (for example, using Postman), and then provide the Guest Token to the SDK. Additionally, the Guest Token is only valid for 5 minutes, so after that you might start facing errors when interacting with the embedded dashboard.
If you would like to avoid adding your credentials to this file, feel free to just skip this step, and provide the guest token directly OR authenticate using a PEM key.
- Replace
your_api_token_herewith a token created (and enabled) from Manager (line 3). - Replace
your_api_secret_herewith its corresponding secret (line 4).
Refer to this page to check how to create your Preset API key/token.
Make sure you have already enabled the Embedded mode for the dashboard you would like to use with this app. Refer to this page for further instructions. Replace these values with the information retrieved from the Embedded modal (from Preset):
- Replace
your_dashboard_id_herewith your Embedded Dashboard ID (line 8). - Replace
your_superset_domain_herewith your Superset Domain (line 9). - Replace
your_preset_team_herewith your Team ID (line 10). - Replace
your_workspace_slug_herewith your Workspace ID (line 11).
- Create a new virtual environment for this project.
- Activate it.
- Run
pip install --upgrade pipto update pip. - Run
pip install -r requirements/requirements.txtto install all dependencies for this project.
- Run
flask --app app run --port=8080 --debugin the terminal (inside the root folder). This would start the Flask app. Feel free to change the--portaccordingly in case8080is already in use. The--debugflag ensures the server automatically reloads when changes are saved to theapp.pyfile. - Access
http://127.0.0.1:8080/on the browser. You should see aniframein the full browser size, which would load the dashboard in embedded mode.
- Once testing is done, press control + C in the terminal to stop the Flask app.
- Deactivate the virtual environment.
Since the Guest Token is only valid for 5 minutes, the SDK automatically refreshes it (when a function to generate a Guest Token is provided). This is configured with the fetchGuestToken parameter. By default, this test app is configured pointing to the fetchGuestTokenFromBackend() function.
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(), // `fetchGuestTokenFromBackend()` is a function that returns a Guest Token
dashboardUiConfig: {},
});If you don't want to add your API credentials to this example app, you can instead provide a Guest Token directly to the fetchGuestToken parameter:
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: () => "{{myGuestToken}}", // Replace `{{myGuestToken}}` with the generated token
dashboardUiConfig: {},
});Refer to this API endpoint documentation to check how to generate a guest token.
Note that the token is only valid for 5 minutes, so since the SDK won't be able to refresh it, you'll start facing errors when trying to interact with the dashboard after that time.
Note: You must have OpenSSL installed to be able to generate the keys.
To authenticate the guest user, two API calls are needed:
- One to authenticate your API credentials and retrieve a
JWTtoken. - Another one that uses this
JWTto generate aguest_token.
It's possible to use a set of public and private PEM keys to generate the guest_token locally and avoid these two calls. Refer to this section if you want to generate the PEM keys on your end, or alternatively run below command to automatically create a key pair:
flask generate-keysThen, use below command to copy the public key:
pbcopy < keys/embedded-example-public-key.pemAccess Preset Manager, click on the three ellipses for your Workspace and select Edit Workspace Settings. Then navigate to the Embedded tab, and paste the public key content. Finally, copy the Key Id visible in the UI, and add it to your .env file.
Then access http://localhost:8081/?auth_type=pem in the browser to load the embedded dashboard using a guest token that's encoded locally.
The Preset SDK has configurations that can be modified to change the embedding experience. These can be configured using the dashboardUiConfig parameter. In this test app, this configuration is currently implemented in the templates/index.html file (line 40):
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(),
dashboardUiConfig: {
// dashboard UI config (all optional)
hideTitle: false, // change it to `true` to hide the dashboard title
hideChartControls: false, // change it to `true` to hide the chart controls (ellipses menu)
filters: {
expanded: true, // change it to `false` so that dashboard filters are collapsed (for vertical filter bar)
visible: true, // change it to `falee` to completely hide the dashboard filter bar
},
urlParams: { // URL parameters to be used with the ``{{url_param()}}`` Jinja macro
param_name: "value",
other_param: "value",
},
showRowLimitWarning: false, // change it to `true` to show the row limit reached warning on charts
},
});The dashboard might load with cached data (if available for the chart, filter and RLS combination). You can pass a URL parameter to force refresh the data:
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(),
dashboardUiConfig: {
urlParams: {
"force": true,
}
}
});By default, a dashboard is loaded in Embedded mode with its default filter configuration. It's possible to pass a permalink_key to load the dashboard with a particular filter configuration:
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(),
dashboardUiConfig: {
urlParams: {
"permalink_key": "aE6zJGOJK3k" // Key generated via the API with the desired filter state
}
}
});It's also possible to retrieve the current data mask configuration (which includes the filter state) at any time using the getDataMask() method:
const dashboardElement = await myLightDashboard; // `myLightDashboard` is a promise that resolves to the dashboard instance
...
const currentDataMaskConfig = await dashboardElement.getDataMask();
console.log('The current data mask configuration for the dashboard is: ', datamask);Alternatively, you can configure the dashboard to automatically emit data mask changes to a method. This is specially useful if you want to monitor filter usage:
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(),
dashboardUiConfig: {
emitDataMasks: true, // When set to true, the dashboard emits filter state changes
}
});
function processDataMaskChange(dataMaskConfig) {
console.log("Received a data mask change from the dashboard:");
console.log(dataMaskConfig);
}
myLightDashboard.then(dashboardElement => {
dashboardElement.observeDataMask(processDataMaskChange);
});It's also possible to validate if the changes emitted were around native filters and/or cross-filters:
function processDataMaskChange(dataMaskConfig) {
console.log("Received a data mask change from the dashboard:");
if (dataMaskConfig.nativeFiltersChanged) {
console.log("Native filters have changed!");
}
if (dataMaskConfig.crossFiltersChanged) {
console.log("Cross filters have changed!");
}
console.log(dataMaskConfig);
}
myLightDashboard.then(dashboardElement => {
dashboardElement.observeDataMask(processDataMaskChange);
});By default, the iframe element is created with its title set to Embedded Dashboard. It's possible to specify a custom title through the iframeTitle parameter:
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(),
iframeTitle: "Preset Embedded Dashboard", // optional: title for the iframe
});Sandbox attributes allow you to change restrictions applied to the iframe element. For example, links clicked in the iframe (even when configured to open in a new tab) are blocked by default. To allow links to successfully load in a new tab, you can include the allow-popups-to-escape-sandbox sandbox property:
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(),
iframeSandboxExtras: ['allow-popups-to-escape-sandbox'],
});Your hosting app might specify a referrerPolicy value for iframes. The Referrer header needs to be included in the iframe request so that the allowed domains are vaildated. The example code in the app.py file already enforces the strict-origin-when-cross-origin value for the referrerPolicy:
const myLightDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(),
referrerPolicy: "strict-origin-when-cross-origin",
});By default, the Guest Token is generated with no RLS applied, and access is only granted to the Dashboard ID specified previously. You can customize the Guest Token configuration in the app.py file, according to the authentication method used:
For guest tokens generated via the API (line 207):
{
"user": {
"username": "test_user",
"first_name": "test",
"last_name": "user"
},
"resources": [
{
"type": "dashboard",
"id": dashboard_id,
}
],
"rls": [
# Apply an RLS to a specific dataset
# { "dataset": dataset_id, "clause": "column = 'filter'" },
# Apply an RLS to all datasets
# { "clause": "column = 'filter'" },
]
}For guest tokens generated using a PEM key (line 254):
{
"user": {
"username": "test_user",
"first_name": "test",
"last_name": "user"
},
"resources": [
{
"type": "dashboard",
"id": dashboard_id
}
],
"rls_rules": [
# Apply an RLS to a specific dataset
# { "dataset": dataset_id, "clause": "column = 'filter'" },
# Apply an RLS to all datasets
# { "clause": "column = 'filter'" },
],
"type": "guest",
"aud": workspace_slug,
}