fix(superset): resolver slug → UUID antes de gerar guest token e embeddar

O SDK do Superset exige o UUID do embedded config (não o slug) tanto no
embedDashboard quanto no guest token. Agora o backend resolve:
  slug → dashboard ID → embedded UUID → guest token com UUID
E retorna { token, embeddedId } para o frontend usar no embedDashboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jonas Pacheco 2026-03-19 14:55:36 -03:00
parent 6e8a3261bb
commit 023451e000
2 changed files with 36 additions and 7 deletions

View File

@ -27,7 +27,8 @@ export function SupersetDashboard({
const [status, setStatus] = useState<"loading" | "ready" | "error" | "unavailable">("loading"); const [status, setStatus] = useState<"loading" | "ready" | "error" | "unavailable">("loading");
const [errorMsg, setErrorMsg] = useState<string>(""); const [errorMsg, setErrorMsg] = useState<string>("");
const fetchGuestToken = useCallback(async (): Promise<string> => { // Busca token e embeddedId (UUID) do backend
const fetchTokenData = useCallback(async (): Promise<{ token: string; embeddedId: string }> => {
const resp = await fetch("/api/superset/guest-token", { const resp = await fetch("/api/superset/guest-token", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@ -38,8 +39,7 @@ export function SupersetDashboard({
const err = await resp.json().catch(() => ({ error: "Erro desconhecido" })); const err = await resp.json().catch(() => ({ error: "Erro desconhecido" }));
throw new Error(err.error || `HTTP ${resp.status}`); throw new Error(err.error || `HTTP ${resp.status}`);
} }
const { token } = await resp.json(); return resp.json();
return token;
}, [dashboardId]); }, [dashboardId]);
const loadDashboard = useCallback(async () => { const loadDashboard = useCallback(async () => {
@ -56,6 +56,9 @@ export function SupersetDashboard({
return; return;
} }
// Resolve embeddedId (UUID) e obtém token inicial
const { embeddedId } = await fetchTokenData();
// Importa SDK dinamicamente (só quando necessário) // Importa SDK dinamicamente (só quando necessário)
const { embedDashboard } = await import("@superset-ui/embedded-sdk"); const { embedDashboard } = await import("@superset-ui/embedded-sdk");
@ -65,10 +68,13 @@ export function SupersetDashboard({
} }
sdkRef.current = await embedDashboard({ sdkRef.current = await embedDashboard({
id: dashboardId, id: embeddedId, // UUID do embedded config (não o slug)
supersetDomain: window.location.origin + "/superset", supersetDomain: window.location.origin + "/superset",
mountPoint: containerRef.current, mountPoint: containerRef.current,
fetchGuestToken, fetchGuestToken: async () => {
const { token } = await fetchTokenData();
return token;
},
dashboardUiConfig: { dashboardUiConfig: {
hideTitle: true, hideTitle: true,
hideChartControls: false, hideChartControls: false,

View File

@ -39,6 +39,29 @@ export function registerSupersetRoutes(app: Express): void {
const token = await getServiceToken(); const token = await getServiceToken();
const user = req.user as any; const user = req.user as any;
// Resolve slug → dashboard ID → embedded UUID
const dashResp = await fetch(`${SUPERSET_URL}/api/v1/dashboard/${dashboardId}`, {
headers: { "Authorization": `Bearer ${token}` },
});
if (!dashResp.ok) {
return res.status(404).json({ error: `Dashboard '${dashboardId}' não encontrado no Superset` });
}
const dashData = await dashResp.json();
const dbNumericId = dashData.result?.id;
const embeddedResp = await fetch(`${SUPERSET_URL}/api/v1/dashboard/${dbNumericId}/embedded`, {
headers: { "Authorization": `Bearer ${token}` },
});
if (!embeddedResp.ok) {
return res.status(404).json({ error: `Dashboard '${dashboardId}' não tem embedding habilitado` });
}
const embeddedData = await embeddedResp.json();
const embeddedUuid = embeddedData.result?.uuid;
if (!embeddedUuid) {
return res.status(404).json({ error: `UUID de embedding não encontrado para '${dashboardId}'` });
}
const guestResp = await fetch(`${SUPERSET_URL}/api/v1/security/guest_token/`, { const guestResp = await fetch(`${SUPERSET_URL}/api/v1/security/guest_token/`, {
method: "POST", method: "POST",
headers: { headers: {
@ -51,7 +74,7 @@ export function registerSupersetRoutes(app: Express): void {
first_name: user?.name?.split(" ")[0] || "Arcádia", first_name: user?.name?.split(" ")[0] || "Arcádia",
last_name: user?.name?.split(" ").slice(1).join(" ") || "User", last_name: user?.name?.split(" ").slice(1).join(" ") || "User",
}, },
resources: [{ type: "dashboard", id: dashboardId }], resources: [{ type: "dashboard", id: embeddedUuid }],
rls: user?.tenantId ? [{ clause: `tenant_id = ${user.tenantId}` }] : [], rls: user?.tenantId ? [{ clause: `tenant_id = ${user.tenantId}` }] : [],
}), }),
}); });
@ -62,7 +85,7 @@ export function registerSupersetRoutes(app: Express): void {
} }
const { token: guestToken } = await guestResp.json(); const { token: guestToken } = await guestResp.json();
res.json({ token: guestToken, supersetUrl: "/superset" }); res.json({ token: guestToken, embeddedId: embeddedUuid, supersetUrl: "/superset" });
} catch (err: any) { } catch (err: any) {
console.error("[Superset] guest-token error:", err.message); console.error("[Superset] guest-token error:", err.message);
res.status(502).json({ error: err.message }); res.status(502).json({ error: err.message });