UNPKG

drizzle-cube

Version:

Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.

552 lines (551 loc) 18.9 kB
import { jsxs as o, jsx as t, Fragment as J } from "react/jsx-runtime"; import { useMemo as j, useState as H, useEffect as T, useCallback as S } from "react"; import Z, { Handle as P, Position as g, getBezierPath as K, BaseEdge as Q, EdgeLabelRenderer as U, useNodesState as _, useEdgesState as ee, addEdge as te, ConnectionMode as se, Controls as ne, MiniMap as re, Background as oe, BackgroundVariant as ae } from "reactflow"; import { u as ie } from "./charts-B8YMw1mi.js"; import O from "dagre"; function ce({ data: i }) { const { cube: s, onFieldClick: f, isHighlighted: n, highlightedFields: w, searchTerm: m } = i, d = () => { }, y = (e, r) => { f && f(s.name, e, r); }, l = (e) => w.includes(e), b = (e) => { if (!m?.trim()) return !0; const r = m.toLowerCase(); return e.name.toLowerCase().includes(r) || e.title && e.title.toLowerCase().includes(r); }, k = (() => { if (!m?.trim()) return !0; const e = s.measures.some((c) => b(c)), r = s.dimensions.some((c) => b(c)); return e || r; })(), N = (e, r, c) => { const v = b(e), u = "px-4 py-2 text-xs cursor-pointer transition-all border-b border-dc-border last:border-b-0"; if (!k && m?.trim()) { if (r) { if (c === "measure") return `${u} bg-dc-warning-bg text-dc-warning`; if (c === "dimension") return e.type === "time" ? `${u} bg-dc-accent-bg text-dc-accent` : `${u} bg-dc-success-bg text-dc-success`; } return `${u} hover:bg-dc-surface-hover text-dc-text-secondary`; } if (m?.trim() && !v) return `${u} opacity-40 hover:opacity-60 text-dc-text-muted`; if (m?.trim() && v && !r) return `${u} font-bold hover:bg-dc-accent-bg`; if (r) { if (c === "measure") return `${u} bg-dc-warning-bg text-dc-warning font-semibold`; if (c === "dimension") return e.type === "time" ? `${u} bg-dc-accent-bg text-dc-accent font-semibold` : `${u} bg-dc-success-bg text-dc-success font-semibold`; } return `${u} hover:bg-dc-surface-hover text-dc-text-secondary`; }; return /* @__PURE__ */ o( "div", { className: ` border-2 rounded-lg shadow-lg min-w-[280px] overflow-hidden transition-all ${!k && m?.trim() ? "opacity-30 grayscale" : ""} ${n ? "border-dc-accent ring-2 ring-dc-accent" : "border-dc-border"} `, style: { backgroundColor: "var(--dc-surface)" }, children: [ /* @__PURE__ */ t( "div", { className: ` px-4 py-3 cursor-pointer transition-colors ${n ? "bg-dc-accent-bg hover:bg-dc-accent-bg" : "bg-dc-surface-secondary hover:bg-dc-surface-hover"} `, onClick: d, children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ o("div", { children: [ /* @__PURE__ */ t("h3", { className: "font-semibold text-dc-text text-sm", children: s.title || s.name }), s.description && /* @__PURE__ */ t("p", { className: "text-xs text-dc-text-muted mt-1 line-clamp-2", children: s.description }) ] }), /* @__PURE__ */ o("div", { className: "text-xs text-dc-text-muted ml-2", children: [ /* @__PURE__ */ o("div", { children: [ s.measures.length, "M" ] }), /* @__PURE__ */ o("div", { children: [ s.dimensions.length, "D" ] }) ] }) ] }) } ), s.measures.length > 0 && /* @__PURE__ */ o("div", { className: "border-t border-dc-border", children: [ /* @__PURE__ */ t("div", { className: "px-4 py-2 bg-dc-warning-bg border-b border-dc-border", children: /* @__PURE__ */ o("h4", { className: "text-xs font-medium text-dc-warning flex items-center", children: [ /* @__PURE__ */ t("span", { className: "w-2 h-2 bg-dc-warning-bg0 rounded-full mr-2" }), "Measures (", s.measures.length, ")" ] }) }), /* @__PURE__ */ t("div", { className: "max-h-32 overflow-y-auto", children: s.measures.map((e) => { const r = e.name.split(".")[1] || e.name, c = l(e.name); return /* @__PURE__ */ t( "div", { className: N(e, c, "measure"), onClick: () => y(r, "measure"), title: e.title, children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ t("span", { className: "font-mono truncate", children: e.shortTitle || e.title || r }), /* @__PURE__ */ t("span", { className: "text-dc-text-muted ml-2 text-[10px] uppercase", children: e.type }) ] }) }, e.name ); }) }) ] }), s.dimensions.filter((e) => e.type === "time").length > 0 && /* @__PURE__ */ o("div", { className: "border-t border-dc-border", children: [ /* @__PURE__ */ t("div", { className: "px-4 py-2 bg-dc-accent-bg border-b border-dc-border", children: /* @__PURE__ */ o("h4", { className: "text-xs font-medium text-dc-accent flex items-center", children: [ /* @__PURE__ */ t("span", { className: "w-2 h-2 bg-dc-accent-bg0 rounded-full mr-2" }), "Time Dimensions (", s.dimensions.filter((e) => e.type === "time").length, ")" ] }) }), /* @__PURE__ */ t("div", { className: "max-h-32 overflow-y-auto", children: s.dimensions.filter((e) => e.type === "time").map((e) => { const r = e.name.split(".")[1] || e.name, c = l(e.name); return /* @__PURE__ */ t( "div", { className: N(e, c, "dimension"), onClick: () => y(r, "dimension"), title: e.title, children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ t("span", { className: "font-mono truncate", children: e.shortTitle || e.title || r }), /* @__PURE__ */ t("span", { className: "text-dc-text-muted ml-2 text-[10px] uppercase", children: e.type }) ] }) }, e.name ); }) }) ] }), s.dimensions.filter((e) => e.type !== "time").length > 0 && /* @__PURE__ */ o("div", { className: "border-t border-dc-border", children: [ /* @__PURE__ */ t("div", { className: "px-4 py-2 bg-dc-success-bg border-b border-dc-border", children: /* @__PURE__ */ o("h4", { className: "text-xs font-medium text-dc-success flex items-center", children: [ /* @__PURE__ */ t("span", { className: "w-2 h-2 bg-dc-success-bg0 rounded-full mr-2" }), "Dimensions (", s.dimensions.filter((e) => e.type !== "time").length, ")" ] }) }), /* @__PURE__ */ t("div", { className: "max-h-32 overflow-y-auto", children: s.dimensions.filter((e) => e.type !== "time").map((e) => { const r = e.name.split(".")[1] || e.name, c = l(e.name); return /* @__PURE__ */ t( "div", { className: N(e, c, "dimension"), onClick: () => y(r, "dimension"), title: e.title, children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ t("span", { className: "font-mono truncate", children: e.shortTitle || e.title || r }), /* @__PURE__ */ t("span", { className: "text-dc-text-muted ml-2 text-[10px] uppercase", children: e.type }) ] }) }, e.name ); }) }) ] }), /* @__PURE__ */ t( P, { type: "source", position: g.Right, id: "right", className: "opacity-0", isConnectable: !1 } ), /* @__PURE__ */ t( P, { type: "target", position: g.Left, id: "left", className: "opacity-0", isConnectable: !1 } ), /* @__PURE__ */ t( P, { type: "source", position: g.Bottom, id: "bottom", className: "opacity-0", isConnectable: !1 } ), /* @__PURE__ */ t( P, { type: "target", position: g.Top, id: "top", className: "opacity-0", isConnectable: !1 } ) ] } ); } function de({ sourceX: i, sourceY: s, targetX: f, targetY: n, sourcePosition: w, targetPosition: m, style: d = {}, data: y, markerEnd: l }) { const [b, E, k] = K({ sourceX: i, sourceY: s, sourcePosition: w, targetX: f, targetY: n, targetPosition: m }); if (!y) return null; const { relationship: N, joinFields: e } = y, r = (C) => { switch (C) { case "belongsTo": return "∈"; // belongs to symbol case "hasOne": return "1:1"; case "hasMany": return "1:M"; default: return "?"; } }, v = ((C) => { switch (C) { case "belongsTo": return "#10b981"; // green case "hasOne": return "#3b82f6"; // blue case "hasMany": return "#f59e0b"; // amber default: return "#6b7280"; } })(N.relationship), u = r(N.relationship); return /* @__PURE__ */ o(J, { children: [ /* @__PURE__ */ t(Q, { path: b, markerEnd: l, style: { ...d, stroke: v } }), /* @__PURE__ */ t(U, { children: /* @__PURE__ */ t( "div", { style: { position: "absolute", transform: `translate(-50%, -50%) translate(${E}px,${k}px)`, fontSize: 10, pointerEvents: "all" }, className: "nodrag nopan", children: /* @__PURE__ */ t( "div", { className: "border-2 rounded-md px-2 py-1 shadow-xs", style: { backgroundColor: "var(--dc-surface)", borderColor: v }, children: /* @__PURE__ */ o("div", { className: "text-center", children: [ /* @__PURE__ */ t( "div", { className: "font-bold text-xs mb-1", style: { color: v }, children: u } ), /* @__PURE__ */ t("div", { className: "text-[9px] text-dc-text-muted leading-tight", children: e.map((C, B) => /* @__PURE__ */ o("div", { className: "font-mono", children: [ C.sourceField, " → ", C.targetField ] }, B)) }) ] }) } ) } ) }) ] }); } const le = { direction: "TB", nodeWidth: 320, nodeHeight: 220, nodeSep: 80, rankSep: 150, ranker: "network-simplex" }; function ue(i, s, f = {}) { const n = j(() => ({ ...le, ...f }), [f]), { layoutedNodes: w, layoutedEdges: m } = j(() => { if (i.length === 0) return { layoutedNodes: [], layoutedEdges: [] }; const d = new O.graphlib.Graph(); return d.setDefaultEdgeLabel(() => ({})), d.setGraph({ rankdir: n.direction, nodesep: n.nodeSep, ranksep: n.rankSep, ranker: n.ranker }), i.forEach((l) => { d.setNode(l.id, { width: n.nodeWidth, height: n.nodeHeight }); }), s.forEach((l) => { d.setEdge(l.source, l.target); }), O.layout(d), { layoutedNodes: i.map((l) => { const b = d.node(l.id); return { ...l, targetPosition: me(n.direction), sourcePosition: he(n.direction), position: { x: b.x - n.nodeWidth / 2, y: b.y - n.nodeHeight / 2 } }; }), layoutedEdges: s }; }, [i, s, n.direction, n.nodeSep, n.rankSep, n.ranker, n.nodeWidth, n.nodeHeight]); return { nodes: w, edges: m }; } function me(i) { switch (i) { case "TB": return g.Top; case "BT": return g.Bottom; case "LR": return g.Left; case "RL": return g.Right; default: return g.Top; } } function he(i) { switch (i) { case "TB": return g.Bottom; case "BT": return g.Top; case "LR": return g.Right; case "RL": return g.Left; default: return g.Bottom; } } const ge = { cubeNode: ce }, pe = { relationshipEdge: de }; function Ce({ className: i = "", onCubeClick: s, onFieldClick: f, highlightedCubes: n = [], highlightedFields: w = [], searchTerm: m }) { const { meta: d, metaLoading: y, metaError: l } = ie(), [b, E] = H({}), [k, N] = H(!1), [e, r] = H(null); T(() => { try { const a = localStorage.getItem("drizzle-cube-erd-node-positions"); a && E(JSON.parse(a)); } catch { } }, []); const { nodes: c, edges: v } = j(() => { if (!d) return { nodes: [], edges: [] }; const a = [], x = []; return d.cubes.forEach((h, p) => { a.push({ id: h.name, type: "cubeNode", position: b[h.name] || { x: p % 3 * 400, y: Math.floor(p / 3) * 300 }, data: { cube: h, onCubeClick: s, onFieldClick: f, isHighlighted: !1, // Will be updated separately highlightedFields: [] // Will be updated separately } }); }), d.cubes.forEach((h) => { h.relationships && h.relationships.forEach((p, M) => { if (p.relationship === "belongsTo") return; const q = `${h.name}-${p.targetCube}-${M}`; x.push({ id: q, source: h.name, target: p.targetCube, type: "relationshipEdge", data: { relationship: p, joinFields: p.joinFields }, animated: !1, style: { stroke: be(p.relationship), strokeWidth: 2 } }); }); }), { nodes: a, edges: x }; }, [d, s, f, b]), u = k || Object.keys(b).length === 0, { nodes: C, edges: B } = ue(c, v, { direction: "LR", nodeWidth: 320, nodeHeight: 220, nodeSep: 100, rankSep: 200 }), L = j(() => (u ? C : c).map((x) => ({ ...x, data: { ...x.data, isHighlighted: n.includes(x.id), highlightedFields: w, searchTerm: m } })), [u, C, c, n, w, m]), F = u ? B : v; T(() => { if (k && L.length > 0) { E({}); try { localStorage.removeItem("drizzle-cube-erd-node-positions"); } catch { } N(!1); } }, [k, L]); const [D, $, z] = _(L), [W, R, A] = ee(F); T(() => { $(L); }, [L, $]), T(() => { R(F); }, [F, R]); const I = S((a) => { z(a), a.filter((h) => h.type === "position" && h.dragging === !1).length > 0 && $((h) => { const p = {}; h.forEach((M) => { M.position && (p[M.id] = M.position); }); try { localStorage.setItem("drizzle-cube-erd-node-positions", JSON.stringify(p)); } catch { } return E(p), h; }); }, [z, $]), V = S( (a) => R((x) => te(a, x)), [R] ), G = S((a) => { a.preventDefault(), a.stopPropagation(), console.log("Context menu triggered at:", a.clientX, a.clientY), r({ x: a.clientX, y: a.clientY }); }, []), X = S(() => { e && r(null); }, [e]), Y = S(() => { N(!0), r(null); }, []); return y ? /* @__PURE__ */ t("div", { className: `flex items-center justify-center h-96 ${i}`, children: /* @__PURE__ */ o("div", { className: "text-center", children: [ /* @__PURE__ */ t("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-dc-accent mx-auto mb-2" }), /* @__PURE__ */ t("p", { className: "text-dc-text-muted", children: "Loading cube schema..." }) ] }) }) : l ? /* @__PURE__ */ t("div", { className: `flex items-center justify-center h-96 ${i}`, children: /* @__PURE__ */ o("div", { className: "text-center text-dc-error", children: [ /* @__PURE__ */ t("p", { className: "font-medium", children: "Failed to load cube schema" }), /* @__PURE__ */ t("p", { className: "text-sm mt-1", children: l }) ] }) }) : !d || d.cubes.length === 0 ? /* @__PURE__ */ t("div", { className: `flex items-center justify-center h-96 ${i}`, children: /* @__PURE__ */ o("div", { className: "text-center text-dc-text-muted", children: [ /* @__PURE__ */ t("p", { className: "font-medium", children: "No cubes found" }), /* @__PURE__ */ t("p", { className: "text-sm mt-1", children: "Register some cubes to see the relationship diagram" }) ] }) }) : /* @__PURE__ */ o("div", { className: `h-full ${i}`, style: { height: "600px" }, children: [ /* @__PURE__ */ t("div", { className: "h-full", children: /* @__PURE__ */ o( Z, { nodes: D, edges: W, onNodesChange: I, onEdgesChange: A, onConnect: V, nodeTypes: ge, edgeTypes: pe, connectionMode: se.Loose, fitView: !0, minZoom: 0.1, maxZoom: 2, defaultViewport: { x: 0, y: 0, zoom: 0.8 }, proOptions: { hideAttribution: !0 }, onPaneContextMenu: G, onPaneClick: X, children: [ /* @__PURE__ */ t(ne, {}), /* @__PURE__ */ t( re, { nodeColor: (a) => n.includes(a.id) ? "#8b5cf6" : "#e5e7eb", maskColor: "rgb(240, 242, 246, 0.7)" } ), /* @__PURE__ */ t(oe, { variant: ae.Dots, gap: 12, size: 1 }) ] } ) }), e && /* @__PURE__ */ t( "div", { className: "fixed z-50 bg-dc-surface rounded-md shadow-lg border border-dc-border py-1 min-w-[120px]", style: { left: e.x, top: e.y }, children: /* @__PURE__ */ t( "button", { onClick: Y, className: "w-full px-3 py-2 text-sm text-dc-text-secondary hover:bg-dc-surface-hover text-left", children: "Auto Layout" } ) } ) ] }); } function be(i) { switch (i) { case "belongsTo": return "#10b981"; // green case "hasOne": return "#3b82f6"; // blue case "hasMany": return "#f59e0b"; // amber default: return "#6b7280"; } } export { Ce as CubeRelationshipDiagram, Ce as default }; //# sourceMappingURL=index-B7NSVb33.js.map