Skip to content

Instantly share code, notes, and snippets.

@redanium
Forked from jackgray/README.md
Created October 11, 2024 12:45
Show Gist options
  • Save redanium/35d3672efc45592f2b0e87a2e8d9fe2b to your computer and use it in GitHub Desktop.
Save redanium/35d3672efc45592f2b0e87a2e8d9fe2b to your computer and use it in GitHub Desktop.

How to enable embedded Superset dashboards in React/Next.js with CSV exporing

Permissions settings on Gamma role:

For Gamma role:

  • can read on EmbeddedDashboard
  • can export on Chart
  • can export on Dashboard
  • can csv on Superset
  • can this form get on CsvToDatabaseView
  • can this form post on CsvToDatabaseView (probably not required but I have it set)
  • can view chart as table on Dashboard (optional but useful)

Some of these may not be explicitly required, as I added many at the same time and haven't experimented which can be removed yet. But it does work with this combination.

role/user for requesting tokens (SUPERSET_USERNAME)

While you can use admin for this, you should really create a new user and role solely for requesting/issuing tokens. Start with admin then strip away the roles that you don't need, or apply the above + roles relating to token requesting/creation. I'm still using broad scope so won't try to tell you the specific ones required.

UI component chart controls

Ensure you disable hiding chart controls by setting hideChartControls: false in the dashboardUiConfig

See SupersetDashboard.tsx

load the keys with a envConfig.ts:

use it in a server-side component:

See dashboardPage.tsx

Ensure your .env file is loaded in the base of the project (next to package.json)

Now the page loads on the server and the dashboard windows load in the client.

superset_config.py

These settings don't play a role in whether CSV exporting appears or not, but if you're having trouble getting the dashboard to load, make sure that both the user requesting the token and the guest user is set to GAMMA role, and that user has the above permissions. I also set the flag PUBLIC_ROLE_LIKE="Gamma" and issued the same perms to that role too, but am not sure if that's required.

Note that for production environments you should not allow all origins and headers, and should make these settings more restrictive

These settings along with ensuring you are using the latest version of Superset (4) should allow CSV exports to work from an embedded dashboard

Slicing CSV exports

You can also configure what a user can filter when exporting from the dashboard in the filter settings for the dashboard. Create a filter for each dimension, and ensure the UI component has showing the filter panel enabled. In the embedded dashboard, when you apply a filter to a chart, the filters are applied whenever you make an export.

import { FC } from 'react';
import SupersetDashboard from "@/components/superset-dashboard";
import { supersetConfig } from "@/lib/envConfig";
const DashboardPage: FC = () => {
return (
<div style={{ width: '100%', height: '100%', overflow: 'auto' }}>
<SupersetDashboard
dashboardTitle="My Dashboard"
supersetUrl={supersetConfig.supersetUrl}
dashboardId={supersetConfig.dashboardId}
username={supersetConfig.username}
password={supersetConfig.password}
guestUsername={supersetConfig.guestUsername}
guestFirstName={supersetConfig.guestFirstName}
guestLastName={supersetConfig.guestLastName}
/>
</div>
);
};
export default DashboardPage;
export const supersetConfig = {
supersetUrl: process.env.SUPERSET_URL || 'https://your-superset-endpoint.com',
username: process.env.SUPERSET_USERNAME || 'admin',
password: process.env.SUPERSET_PASSWORD || 'admin',
guestUsername: process.env.SUPERSET_GUEST_USERNAME || 'guestUser',
guestFirstName: process.env.SUPERSET_GUEST_FNAME || 'Guest',
guestLastName: process.env.SUPERSET_GUEST_LNAME || 'User',
};
SESSION_COOKIE_SAMESITE = None
ENABLE_PROXY_FIX = True
FEATURE_FLAGS = { "EMBEDDED_SUPERSET": True }
ENABLE_CORS = True
CORS_OPTIONS = {
"supports_credentials": True,
"allow_headers": ["*"],
"resources": ["*"],
'origins': ['https://your-app-domain(s).com'],
}
GUEST_ROLE_NAME= 'Gamma'
GUEST_TOKEN_JWT_EXP_SECONDS = 3600 # 1 hour
PUBLIC_ROLE_LIKE = "Gamma"
# Allow embedding in iframes from any origin (probably want to restrict this for production)
# Flask-WTF flag for CSRF
WTF_CSRF_ENABLED = False
# Add endpoints that need to be exempt from CSRF protection (restrict for production)
WTF_CSRF_EXEMPT_LIST = ['*']
# # A CSRF token that expires in 1 year
# WTF_CSRF_TIME_LIMIT = 60 * 60 * 24 * 365
# Set this API key to enable Mapbox visualizations
# MAPBOX_API_KEY = ''
OVERRIDE_HTTP_HEADERS = {'Content-Security-Policy': 'frame-ancestors https://your-app-domain(s)'}
TALISMAN_ENABLED = False
HTTP_HEADERS={}
'use client';
import { useEffect, useRef, useState } from 'react';
import axios from 'axios';
import { embedDashboard } from "@superset-ui/embedded-sdk";
interface SupersetDashboardProps {
supersetUrl: string;
dashboardId: string;
username: string;
password: string;
guestUsername: string;
guestFirstName: string;
guestLastName: string;
dashboardTitle: string;
}
const SupersetDashboard = ({
dashboardTitle,
supersetUrl,
dashboardId,
username,
password,
guestUsername,
guestFirstName,
guestLastName
}: SupersetDashboardProps) => {
const divRef = useRef<HTMLIFrameElement>(null);
useEffect(() => {
const supersetApiUrl = supersetUrl + '/api/v1/security';
const fetchAccessToken = async () => {
const body = {
username,
password,
provider: "db",
refresh: true,
};
const config = {
headers: {
"Content-Type": "application/json"
}
};
const { data } = await axios.post(supersetApiUrl + '/login', body, config);
return data.access_token;
};
const fetchGuestToken = async (accessToken: string) => {
const guestTokenBody = {
resources: [
{
type: "dashboard",
id: dashboardId,
}
],
rls: [], // Add your RLS filters here if needed
user: {
username: guestUsername,
first_name: guestFirstName,
last_name: guestLastName,
}
};
const guestTokenHeaders = {
headers: {
"Content-Type": "application/json",
"Authorization": 'Bearer ' + accessToken
}
};
const response = await axios.post(supersetApiUrl + '/guest_token/', guestTokenBody, guestTokenHeaders);
return response.data.token;
};
const initializeDashboard = async () => {
try {
const accessToken = await fetchAccessToken();
const guestToken = await fetchGuestToken(accessToken);
const mountPoint = document.getElementById("superset-container") as HTMLElement;
if (mountPoint) {
embedDashboard({
id: dashboardId,
supersetDomain: supersetUrl,
mountPoint: mountPoint,
fetchGuestToken: () => guestToken,
dashboardUiConfig: {
hideTitle: false,
hideChartControls: false,
hideTab: false,
filters: {
expanded: false,
visible: false
}
},
});
// Ensure iframe is properly sized
const iframeSuperset = mountPoint.children[0] as HTMLIFrameElement;
if (iframeSuperset) {
iframeSuperset.style.width = "100%";
iframeSuperset.style.height = "100%";
}
} else {
console.error("Mount point element not found");
}
} catch (error) {
console.error("Error initializing the dashboard:", error);
}
};
initializeDashboard();
}, [supersetUrl, dashboardId, username, password, guestUsername, guestFirstName, guestLastName]);
return (
<div
ref={divRef}
title={dashboardTitle}
id="superset-container"
style={{
width: '100%',
height: '100vh',
overflow: 'hidden',
}}
/>
);
};
export default SupersetDashboard;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment