React SDK
@concepttocloud/saiku-embed-react é um wrapper React tipado em
volta do custom element <saiku-embed> existente. O
elemento já funciona em React — React 18+ encaminha atributos
desconhecidos direto para o DOM — mas times React avaliam contra
npm install + imports tipados, não tags <script>. Este SDK fecha
essa lacuna sem mudar o runtime subjacente.
Distribuído no saiku v4.7 como saiku#1432.
Instalar
npm install @concepttocloud/saiku-embed-react reactO pacote peer-depende de React 18+. O base
@concepttocloud/saiku-embed é uma dependência de runtime e é puxado
automaticamente.
Usar
import { SaikuEmbed } from "@concepttocloud/saiku-embed-react";
function Dashboard({ token }: { token: string }) { return ( <SaikuEmbed server="https://YOUR-WORKSPACE.saiku.bi" token={token} path="homes/admin/Sales.saiku" render="chart" mode="bar" height="480px" /> );}Importar o pacote tem o efeito colateral de registrar o custom element
subjacente — você não precisa de um import "@concepttocloud/saiku-embed" separado a menos que você também queira a
tag disponível fora da árvore React.
Props
| Prop | Tipo | Default | Notas |
|---|---|---|---|
server | string | opcional | Origin do launcher do Saiku. Omita para embeds same-origin. |
path | string | obrigatório | Query path / dashboard path / cube ref dependendo de kind. |
kind | "query" | "dashboard" | "ai" | "query" | Seleciona o sabor do embed. |
token | string | opcional | Token de embed cunhado server-side. Omita para grants públicos. |
render | "table" | "matrix" | "chart" | "table" | Só significativo para kind="query". |
mode | "bar" | "line" | "pie" | "bar" | Só significativo para render="chart". |
height | string | "400px" | Altura CSS da superfície renderizada. |
style | React.CSSProperties | opcional | Prop style padrão do React. |
className | string | opcional | Prop className padrão do React. |
id | string | opcional | Passado adiante para seletores e2e. |
data-testid | string | opcional | Passado adiante para o nó DOM. |
Declarações de tipo completas são distribuídas no index.d.ts do
pacote.
Kinds
Query salva como tabela
<SaikuEmbed server="https://saiku.example.com" token={token} path="homes/admin/Sales.saiku" height="400px"/>Gráfico
<SaikuEmbed server="https://saiku.example.com" token={token} path="homes/admin/Sales.saiku" render="chart" mode="bar" height="500px"/>Matrix
O modo matrix preserva a estrutura de eixo linha / coluna — measures em
colunas, membros de dimensão em linhas — em vez de achatar para um
único mapa de row-key como render="table" faz. Útil para relatórios
estilo pivô.
<SaikuEmbed server="https://saiku.example.com" token={token} path="homes/admin/Sales.saiku" render="matrix" height="500px"/>Widget de ask AI
Aponte o token para um cubo (em vez de uma query salva) e insira uma caixa de ask em inglês simples. Exige um token de embed do tipo AI e um launcher com um provider de LLM configurado.
<SaikuEmbed server="https://saiku.example.com" token={aiToken} kind="ai" path="foodmart/FoodMart/FoodMart/Sales" height="240px"/>Dashboard salvo
<SaikuEmbed server="https://saiku.example.com" token={token} kind="dashboard" path="homes/admin/exec.saikudash" height="700px"/>Embed público anônimo
Se o recurso está marcado como publicamente embeddable no servidor (veja Embeds públicos), omita o token inteiramente:
<SaikuEmbed server="https://saiku.example.com" path="shared/public-chart.saiku" render="chart"/>Cunhando um token a partir do seu servidor
mintEmbedToken() é um helper de Node / edge-function para o caso muito
comum de cunhar um token de embed em nome de um usuário final antes de
renderizar <SaikuEmbed>.
import { mintEmbedToken } from "@concepttocloud/saiku-embed-react";
export async function POST(request: Request) { const auth = "Basic " + Buffer.from( `${process.env.SAIKU_USER}:${process.env.SAIKU_PASS}`, ).toString("base64");
const { token, expiresAt } = await mintEmbedToken({ server: process.env.SAIKU_URL!, authorization: auth, resourceKind: "query", resourcePath: "homes/admin/Sales.saiku", ttlHours: 24, label: "Public marketing page", });
return Response.json({ token, expiresAt });}import { mintEmbedToken } from "@concepttocloud/saiku-embed-react";
export async function loader() { const auth = "Basic " + Buffer.from( `${process.env.SAIKU_USER}:${process.env.SAIKU_PASS}`, ).toString("base64");
const { token } = await mintEmbedToken({ server: process.env.SAIKU_URL!, authorization: auth, resourceKind: "query", resourcePath: "homes/admin/Sales.saiku", });
return { token };}import { mintEmbedToken } from "@concepttocloud/saiku-embed-react";
export default { async fetch(request: Request, env: Env) { const auth = "Basic " + btoa(`${env.SAIKU_USER}:${env.SAIKU_PASS}`); const { token } = await mintEmbedToken({ server: env.SAIKU_URL, authorization: auth, resourceKind: "query", resourcePath: "homes/admin/Sales.saiku", fetch: fetch.bind(globalThis), }); return Response.json({ token }); },};No lado do cliente o token cai na prop token:
"use client";import useSWR from "swr";import { SaikuEmbed } from "@concepttocloud/saiku-embed-react";
export default function EmbedTile() { const { data } = useSWR("/api/embed-token", (u) => fetch(u, { method: "POST" }).then((r) => r.json())); if (!data?.token) return <div>Loading…</div>; return <SaikuEmbed server="…" token={data.token} path="homes/admin/Sales.saiku" />;}Opções de mintEmbedToken
| Opção | Tipo | Notas |
|---|---|---|
server | string | Base URL do launcher. |
authorization | string | Valor para o header Authorization (Basic … ou Bearer …). |
resourceKind | "query" | "dashboard" | "ai" | Tipo de recurso que o token fixa. |
resourcePath | string | Path (para query/dashboard) ou cube ref (para ai). |
ttlHours | number (opcional) | Tempo de vida do token; o default do servidor é 72h. |
label | string (opcional) | Label legível que a UI de admin mostra ao lado do token. |
fetch | typeof fetch (opcional) | Sobrescrevível para testes + runtimes não-navegador (ex.: Cloudflare Workers). |
Retorna { token, expiresAt }. Lança se o servidor retorna não-2xx ou
o corpo da resposta não é um envelope de token.
Autocomplete na tag crua
O pacote aumenta tanto o global JSX.IntrinsicElements
(React 17/18) quanto React.JSX.IntrinsicElements (React 19+) para que
o custom element cru receba o mesmo conjunto de props tipadas que
<SaikuEmbed>. Use o que preferir:
// Typed React component<SaikuEmbed server="…" token={token} path="…" render="chart" />
// Or the raw custom element (also typed)<saiku-embed server="…" token={token} path="…" render="chart" />Theming
O embed vive dentro de um shadow root, então o CSS da página host não pode vazar para dentro. Recolora via propriedades CSS customizadas no wrapper:
<SaikuEmbed server="…" token={token} path="…" style={{ "--saiku-embed-fg": "#0f172a", "--saiku-embed-bg": "transparent", "--saiku-embed-border": "#cbd5e1", "--saiku-embed-header-bg": "#f1f5f9", "--saiku-embed-tile-bg": "#ffffff", "--saiku-embed-row-hover": "#e2e8f0", "--saiku-embed-negative": "#b91c1c", } as React.CSSProperties}/>A lista completa de variáveis temáveis está na página do embed base.
Fixação de versão
A versão do React SDK acompanha o release base
@concepttocloud/saiku-embed um-para-um. 3.19.0 do SDK usa 3.19.0
do runtime base — a dep de runtime é fixada no momento do release para
que um consumidor nunca possa acidentalmente misturar majors entre os
dois.
Tamanho de bundle
- Wrapper: ~1 KB gzipped (o runtime inteiro é uma única chamada
React.createElement). - Custom element base: ~213 KB gzipped (runtime Svelte 5 CE + ECharts + os renderers do embed).
- React: peer dep — não contado contra nenhum dos pacotes.
Não-objetivos para v1
- React hooks para resultados de query (
useSaikuQuery) — o custom element gerencia seu próprio estado; um hook de dados tipado duplica a superfície AI Query API. Follow-up se alguém pedir. - Variante server-component —
<SaikuEmbed>é browser-only (ele renderiza um shadow root). Dados iniciais renderizados no servidor para hidratação são um follow-up se o padrão se provar doloroso. - Divisão de bundle por-kind — o wrapper atual sempre puxa o bundle base completo. Tree-shaking por kind é uma mudança do pacote-base, não uma mudança do wrapper.