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

View File

@ -39,6 +39,29 @@ export function registerSupersetRoutes(app: Express): void {
const token = await getServiceToken();
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/`, {
method: "POST",
headers: {
@ -51,7 +74,7 @@ export function registerSupersetRoutes(app: Express): void {
first_name: user?.name?.split(" ")[0] || "Arcádia",
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}` }] : [],
}),
});
@ -62,7 +85,7 @@ export function registerSupersetRoutes(app: Express): void {
}
const { token: guestToken } = await guestResp.json();
res.json({ token: guestToken, supersetUrl: "/superset" });
res.json({ token: guestToken, embeddedId: embeddedUuid, supersetUrl: "/superset" });
} catch (err: any) {
console.error("[Superset] guest-token error:", err.message);
res.status(502).json({ error: err.message });