React SDK
@concepttocloud/saiku-embed-react to typowany wrapper React wokół
istniejącego custom elementu <saiku-embed>. Element już
działa w Reakcie — React 18+ przekazuje nieznane atrybuty prosto do DOM
— ale zespoły Reactowe oceniają względem npm install + typowanych
importów, nie tagów <script>. To SDK zamyka tę lukę bez zmiany
bazowego runtime.
Dostarczane w saiku v4.7 jako saiku#1432.
Instalacja
npm install @concepttocloud/saiku-embed-react reactPakiet peer-zależy od React 18+. Bazowe @concepttocloud/saiku-embed
jest zależnością runtime i jest wciągane automatycznie.
Użycie
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" /> );}Import pakietu ma efekt uboczny w postaci rejestracji bazowego custom
elementu — nie potrzebujesz osobnego
import "@concepttocloud/saiku-embed", chyba że chcesz też, aby tag był
dostępny poza drzewem React.
Propsy
| Prop | Typ | Domyślnie | Uwagi |
|---|---|---|---|
server | string | opcjonalny | Origin launchera Saiku. Pomiń dla embedów tego samego origin. |
path | string | wymagany | Ścieżka zapytania / ścieżka dashboardu / referencja kostki zależnie od kind. |
kind | "query" | "dashboard" | "ai" | "query" | Wybiera odmianę embedu. |
token | string | opcjonalny | Token embedu wybity po stronie serwera. Pomiń dla publicznych grantów. |
render | "table" | "matrix" | "chart" | "table" | Ma znaczenie tylko dla kind="query". |
mode | "bar" | "line" | "pie" | "bar" | Ma znaczenie tylko dla render="chart". |
height | string | "400px" | Wysokość CSS renderowanej powierzchni. |
style | React.CSSProperties | opcjonalny | Standardowy prop style Reacta. |
className | string | opcjonalny | Standardowy prop className Reacta. |
id | string | opcjonalny | Przekazywany dla selektorów e2e. |
data-testid | string | opcjonalny | Przekazywany do węzła DOM. |
Pełne deklaracje typów dostarczane są w pliku index.d.ts pakietu.
Rodzaje
Zapisane zapytanie jako tabela
<SaikuEmbed server="https://saiku.example.com" token={token} path="homes/admin/Sales.saiku" height="400px"/>Wykres
<SaikuEmbed server="https://saiku.example.com" token={token} path="homes/admin/Sales.saiku" render="chart" mode="bar" height="500px"/>Macierz
Tryb macierzy zachowuje strukturę osi wierszy / kolumn — miary na
kolumnach, członkowie wymiaru na wierszach — zamiast spłaszczać do
pojedynczej mapy klucz-wiersza jak robi to render="table". Przydatne
dla raportów w stylu pivota.
<SaikuEmbed server="https://saiku.example.com" token={token} path="homes/admin/Sales.saiku" render="matrix" height="500px"/>Widżet AI ask
Wskaż token na kostkę (zamiast na zapisane zapytanie) i wrzuć pole ask w prostym języku. Wymaga tokenu embedu rodzaju AI i launchera ze skonfigurowanym dostawcą LLM.
<SaikuEmbed server="https://saiku.example.com" token={aiToken} kind="ai" path="foodmart/FoodMart/FoodMart/Sales" height="240px"/>Zapisany dashboard
<SaikuEmbed server="https://saiku.example.com" token={token} kind="dashboard" path="homes/admin/exec.saikudash" height="700px"/>Anonimowy publiczny embed
Jeśli zasób jest oznaczony jako publicznie osadzalny na serwerze (zobacz Publiczne embedy), pomiń token całkowicie:
<SaikuEmbed server="https://saiku.example.com" path="shared/public-chart.saiku" render="chart"/>Wybijanie tokenu z twojego serwera
mintEmbedToken() to pomocnik Node / edge-function dla bardzo częstego
przypadku wybijania tokenu embedu w imieniu użytkownika końcowego przed
wyrenderowaniem <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 }); },};Po stronie klienta token ląduje w propsie 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" />;}Opcje mintEmbedToken
| Opcja | Typ | Uwagi |
|---|---|---|
server | string | Bazowy URL launchera. |
authorization | string | Wartość nagłówka Authorization (Basic … lub Bearer …). |
resourceKind | "query" | "dashboard" | "ai" | Rodzaj zasobu, do którego token jest przypięty. |
resourcePath | string | Ścieżka (dla query/dashboard) lub referencja kostki (dla ai). |
ttlHours | number (opcjonalne) | Czas życia tokenu; domyślnie serwera to 72h. |
label | string (opcjonalne) | Czytelna etykieta, którą UI admina pokazuje obok tokenu. |
fetch | typeof fetch (opcjonalne) | Nadpisywalne dla testów + runtime’ów spoza przeglądarki (np. Cloudflare Workers). |
Zwraca { token, expiresAt }. Rzuca wyjątek, jeśli serwer zwróci
non-2xx lub ciało odpowiedzi nie jest kopertą tokenu.
Autouzupełnianie na surowym tagu
Pakiet rozszerza zarówno globalne JSX.IntrinsicElements (React 17/18),
jak i React.JSX.IntrinsicElements (React 19+), więc surowy custom
element dostaje ten sam typowany zbiór propsów co <SaikuEmbed>. Użyj,
którego wolisz:
// 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" />Motywy (theming)
Embed żyje wewnątrz shadow root, więc CSS strony hosta nie może przeciekać do środka. Przekoloruj przez niestandardowe właściwości CSS na wrapperze:
<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}/>Pełna lista zmiennych do motywowania jest na stronie bazowego embedu.
Przypinanie wersji
Wersja React SDK śledzi wydanie bazowego @concepttocloud/saiku-embed
jeden-do-jednego. 3.19.0 SDK używa 3.19.0 bazowego runtime — zależność
runtime jest przypięta w czasie wydania, więc konsument nigdy nie może
przypadkowo pomieszać wersji major między tymi dwoma.
Rozmiar bundle
- Wrapper: ~1 KB po gzip (cały runtime to pojedyncze wywołanie
React.createElement). - Bazowy custom element: ~213 KB po gzip (runtime CE Svelte 5 + ECharts + renderery embedu).
- React: peer dep — nie liczony przeciwko żadnemu z pakietów.
Nie-cele dla v1
- Hooki React dla wyników zapytań (
useSaikuQuery) — custom element obsługuje własny stan; typowany hook danych duplikuje powierzchnię AI Query API. Kontynuacja, jeśli ktoś poprosi. - Wariant server-component —
<SaikuEmbed>działa tylko w przeglądarce (renderuje shadow root). Server-renderowane dane początkowe do hydracji to kontynuacja, jeśli wzorzec okaże się bolesny. - Rozdzielanie bundle per rodzaj — obecny wrapper zawsze wciąga pełny bazowy bundle. Tree-shaking według rodzaju to zmiana pakietu bazowego, nie zmiana wrappera.