UNPKG

datajunction-ui

Version:
1,400 lines (1,399 loc) • 47.5 kB
var Fe = Object.defineProperty; var Ae = (n, i, o) => i in n ? Fe(n, i, { enumerable: !0, configurable: !0, writable: !0, value: o }) : n[i] = o; var fe = (n, i, o) => Ae(n, typeof i != "symbol" ? i + "" : i, o); import { jsxs as y, jsx as e, Fragment as K } from "react/jsx-runtime"; import { cz as W, bp as ge, o as q, q as U, W as ye, a9 as Re, aj as qe, b4 as Oe, j as _e, cg as Le, n as je, u as $e, ci as C, D as P, bo as Ue, t as Q, cv as Pe, cy as ze, b as Je, N as be, s as Ve, r as Be, aH as Qe, z as He } from "./index-n79cxNwC.js"; import T, { useContext as z, useState as A, useEffect as $, useMemo as Ke } from "react"; import { useNavigate as We, useParams as Ze } from "react-router-dom"; import { F as Ge } from "./FullNameField-fHyJsZmF.js"; const Ye = ({ djClient: n, value: i }) => { const [o, c] = T.useState({}), [d, s] = T.useState([]), l = W(), r = l.values.upstream_node, m = T.useMemo(() => ge.sql({ schema: o }), [o]); T.useEffect(() => { async function v() { try { const N = await n.metrics(); s(N || []); } catch (N) { console.error("Failed to load metrics for autocomplete:", N); } } v(); }, [n]), T.useEffect(() => { let v = !1; async function N() { var D; const w = {}; if (r && r.trim() !== "") try { const R = await n.node(r); (D = R == null ? void 0 : R.columns) == null || D.forEach((J) => { w[J.name] = []; }); } catch (R) { console.error("Failed to load upstream node columns:", R); } d.forEach((R) => { w[R] = []; }), v || c(w); } return N(), () => { v = !0; }; }, [n, r, d]); const h = (v) => { l.setFieldValue("aggregate_expression", v); }, f = !r || r.trim() === ""; return /* @__PURE__ */ y("div", { className: "QueryInput MetricQueryInput NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "query", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "Query", children: f ? "Derived Metric Expression *" : "Aggregate Expression *" }), /* @__PURE__ */ e( "p", { className: "field-help-text", style: { fontSize: "0.85em", color: "#666", marginBottom: "8px" }, children: f ? "Reference other metrics using their full names (e.g., namespace.metric_name / namespace.other_metric)" : "Use aggregate functions on columns from the upstream node (e.g., SUM(column_name))" } ), /* @__PURE__ */ e( U, { type: "textarea", style: { display: "none" }, as: "textarea", name: "aggregate_expression", id: "Query" } ), /* @__PURE__ */ e("div", { role: "button", tabIndex: 0, className: "relative flex bg-[#282a36]", children: /* @__PURE__ */ e( ye, { id: "aggregate_expression", name: "aggregate_expression", extensions: [m], value: i, options: { theme: "default", lineNumbers: !0 }, width: "100%", height: "100px", style: { margin: "0 0 23px 0", flex: 1, fontSize: "110%", textAlign: "left" }, onChange: (v, N) => { h(v); } } ) }) ] }); }, Xe = /`?([a-zA-Z_][a-zA-Z0-9_]*)`?\.`?([a-zA-Z_][a-zA-Z0-9_]*)`?\.`?([a-zA-Z_][a-zA-Z0-9_]*)`?/; function et(n, i) { const o = new RegExp(Xe.source, "g"), c = new Set(i.map((r) => r.toLowerCase())), d = /* @__PURE__ */ new Set(), s = []; let l; for (; (l = o.exec(n)) !== null; ) { const [, r, m, h] = l; if (!c.has(r.toLowerCase())) continue; const f = `${r}.${m}.${h}`; d.has(f) || (d.add(f), s.push([r, m, h])); } return s; } const oe = /`?[a-zA-Z_][a-zA-Z0-9_]*`?(?:\.`?[a-zA-Z_][a-zA-Z0-9_]*`?)+/g; function Ie(n) { return n.replace(/`/g, ""); } const he = Re.define(); function tt(n) { switch (n) { case "registering": return "dj-node-chip--registering"; case "valid": return "dj-node-chip--valid"; case "warning": return "dj-node-chip--warning"; case "invalid": return "dj-node-chip--invalid"; default: return "dj-node-chip--unknown"; } } function Ce(n, i, o) { const c = new Set((o() || []).map((l) => l.toLowerCase())), d = n.state.doc, s = []; for (const { from: l, to: r } of n.visibleRanges) { const m = d.sliceString(l, r); oe.lastIndex = 0; let h; for (; (h = oe.exec(m)) !== null; ) { const f = h[0], _ = Ie(f), b = i(_), v = _.split("."); let N; if (b != null && b.refType) N = b.refType; else if (v.length === 3 && c.has(v[0].toLowerCase())) N = "source"; else continue; const w = `dj-node-chip dj-node-chip--${N} ${tt( b == null ? void 0 : b.kind )}`, D = l + h.index, R = D + f.length; s.push({ from: D, to: R, deco: _e.mark({ class: w, attributes: b != null && b.message ? { title: b.message } : void 0 }) }); } } return s.sort((l, r) => l.from - r.from || l.to - r.to), _e.set(s.map((l) => l.deco.range(l.from, l.to))); } function nt({ getStatus: n, getKnownCatalogs: i }) { return qe.fromClass( class { constructor(o) { this.decorations = Ce(o, n, i); } update(o) { const c = o.transactions.some( (d) => d.effects.some((s) => s.is(he)) ); (o.docChanged || o.viewportChanged || c) && (this.decorations = Ce( o.view, n, i )); } }, { decorations: (o) => o.decorations } ); } function at(n, i) { const o = n.state.doc.lineAt(i); oe.lastIndex = 0; let c; for (; (c = oe.exec(o.text)) !== null; ) { const d = o.from + c.index, s = d + c[0].length; if (i >= d && i <= s) return { key: Ie(c[0]), from: d, to: s }; } return null; } function xe(n, i) { var h; const o = document.createElement("div"); o.className = "dj-node-tooltip"; const c = n.node || {}, d = document.createElement("div"); d.className = "dj-node-tooltip__header"; const s = document.createElement("span"); s.className = `dj-node-chip dj-node-chip--${n.refType || "node"}`, s.textContent = n.refType || "node", d.appendChild(s); const l = document.createElement("span"); if (l.className = "dj-node-tooltip__name", l.textContent = c.name || i, d.appendChild(l), o.appendChild(d), n.kind === "invalid") { const f = document.createElement("div"); return f.className = "dj-node-tooltip__note", f.textContent = "Not found in DJ.", o.appendChild(f), o; } if (n.kind === "registering") { const f = document.createElement("div"); return f.className = "dj-node-tooltip__note", f.textContent = "Registering…", o.appendChild(f), o; } const r = document.createElement("div"); r.className = "dj-node-tooltip__grid"; const m = (f, _) => { if (_ == null || _ === "") return; const b = document.createElement("div"); b.className = "dj-node-tooltip__cell"; const v = document.createElement("span"); v.className = "dj-node-tooltip__label", v.textContent = f; const N = document.createElement("span"); N.className = "dj-node-tooltip__value", N.textContent = _, b.appendChild(v), b.appendChild(N), r.appendChild(b); }; if (c.status && m("Status", c.status), c.version && m("Version", c.version), c.mode && m("Mode", c.mode), Array.isArray(c.columns) && m("Columns", String(c.columns.length)), r.childElementCount > 0 && o.appendChild(r), c.description) { const f = document.createElement("div"); f.className = "dj-node-tooltip__description"; const _ = c.description; f.textContent = _.length > 280 ? _.slice(0, 277) + "…" : _, o.appendChild(f); } if (Array.isArray(c.columns) && c.columns.length > 0) { const f = document.createElement("div"); f.className = "dj-node-tooltip__columns"; for (const _ of c.columns) { const b = document.createElement("div"); b.className = "dj-node-tooltip__col-row"; const v = document.createElement("span"); if (v.className = "dj-node-tooltip__col-name", v.textContent = _.name, b.appendChild(v), _.dimension || _.dimension_column) { const w = document.createElement("span"); w.className = "dj-node-tooltip__col-tag", w.textContent = "dim", w.title = (h = _.dimension) != null && h.name ? `Links to ${_.dimension.name}` : "Linked dimension", b.appendChild(w); } const N = document.createElement("span"); N.className = "dj-node-tooltip__col-type", N.textContent = (_.type || "").split(` `)[0], N.title = _.type || "", b.appendChild(N), f.appendChild(b); } o.appendChild(f); } if (c.name) { const f = document.createElement("div"); f.className = "dj-node-tooltip__footer"; const _ = document.createElement("a"); _.href = `/nodes/${encodeURIComponent(c.name)}`, _.target = "_blank", _.rel = "noreferrer noopener", _.textContent = "Open node →", f.appendChild(_), o.appendChild(f); } return o; } const ve = /* @__PURE__ */ new Map(); function rt({ getStatus: n, getKnownCatalogs: i, fetchNodeDetails: o }) { return Oe( (c, d) => { const s = at(c, d); if (!s) return null; const l = n(s.key); if (!l) { const r = new Set( (i() || []).map((h) => h.toLowerCase()) ), m = s.key.split("."); if (m.length !== 3 || !r.has(m[0].toLowerCase())) return null; } return { pos: s.from, end: s.to, above: !0, create: () => { const r = l || { refType: "source" }, m = xe(r, s.key); if (o && r.kind !== "invalid" && r.kind !== "registering") { const h = ve.get(s.key), f = h || o(s.key); h || ve.set(s.key, f), Promise.resolve(f).then((_) => { if (!_) return; const b = xe( { ...r, node: { ...r.node || {}, ..._ } }, s.key ); m.parentNode ? m.parentNode.replaceChild(b, m) : m.replaceChildren(...b.childNodes); }).catch(() => { }); } return { dom: m }; } }; }, { hoverTime: 150 } ); } const M = { bg: "#fbfbfd", surface: "#f6f6f9", gutter: "#9aa2b1", selection: "#dbe2ec", caret: "#3b3f46", text: "#27303d", muted: "#8b94a5", keyword: "#6b3aa0", // matches cube/dimension violet family string: "#0e7c5a", // forest green number: "#a96621", // warm amber (same as dimension chip) operator: "#475569", functionName: "#0063b4", // transform blue punctuation: "#5c6573", comment: "#9ca3af" }, ot = je.theme( { "&": { color: M.text, backgroundColor: M.bg, fontSize: "13px" }, ".cm-content": { caretColor: M.caret, fontFamily: '"JetBrains Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", monospace' }, ".cm-cursor, .cm-dropCursor": { borderLeftColor: M.caret }, "&.cm-focused .cm-selectionBackground, ::selection, .cm-selectionBackground": { backgroundColor: M.selection }, ".cm-activeLine": { backgroundColor: "rgba(99, 102, 241, 0.05)" }, ".cm-gutters": { backgroundColor: M.surface, color: M.gutter, border: "none" }, ".cm-activeLineGutter": { backgroundColor: "rgba(99, 102, 241, 0.08)", color: M.text }, ".cm-lineNumbers .cm-gutterElement": { padding: "0 8px 0 6px" }, ".cm-foldGutter .cm-gutterElement": { color: M.muted }, ".cm-tooltip": { background: "#ffffff", border: "1px solid #e2e8f0", borderRadius: "6px", boxShadow: "0 4px 12px rgba(15, 23, 42, 0.06)" }, ".cm-tooltip-autocomplete > ul > li[aria-selected]": { background: "#eef2ff", color: M.text }, ".cm-matchingBracket, .cm-nonmatchingBracket": { backgroundColor: "#fef3c7", outline: "none" }, ".cm-panels": { backgroundColor: M.surface, color: M.text } }, { dark: !1 } ), st = $e.define([ { tag: [C.keyword, C.modifier, C.controlKeyword, C.operatorKeyword], color: M.keyword, fontWeight: "500" }, { tag: [C.string, C.special(C.string)], color: M.string }, { tag: [C.number, C.bool, C.null], color: M.number }, { tag: [C.function(C.variableName), C.function(C.propertyName)], color: M.functionName }, { tag: [C.operator, C.compareOperator, C.arithmeticOperator, C.logicOperator], color: M.operator }, { tag: [C.punctuation, C.paren, C.brace, C.bracket, C.separator], color: M.punctuation }, { tag: [C.variableName, C.propertyName, C.attributeName], color: M.text }, { tag: [C.typeName, C.className], color: M.functionName }, { tag: [C.comment, C.lineComment, C.blockComment], color: M.comment, fontStyle: "italic" }, { tag: C.invalid, color: "#cc2222" } ]), it = [ ot, Le(st) ], we = { INVALID_SQL_QUERY: "Invalid SQL", TYPE_INFERENCE: "Type inference", INVALID_COLUMN: "Invalid column", INVALID_ARGUMENTS_TO_FUNCTION: "Invalid function arguments", MISSING_COLUMNS: "Missing columns", UNKNOWN_NODE: "Unknown node", MISSING_PARENT: "Missing parent", NODE_TYPE_ERROR: "Node type", NOT_IMPLEMENTED_ERROR: "Not implemented" }, ct = (n) => { if (n == null || n === "") return "Error"; const i = String(n); return we[i] ? we[i] : i.toLowerCase().split("_").map((o) => o ? o[0].toUpperCase() + o.slice(1) : "").join(" "); }, lt = ({ djClient: n, value: i }) => { const [o, c] = T.useState({}), d = W(), s = T.useMemo(() => ge.sql({ schema: o }), [o]), l = T.useRef(null), r = T.useRef(null), m = T.useRef(/* @__PURE__ */ new Set()), h = T.useRef([]), [f, _] = T.useState([]), [b, v] = T.useState({}), N = T.useRef(b); T.useEffect(() => { N.current = b; }, [b]); const w = T.useRef(null), D = T.useCallback((x, F) => { N.current = { ...N.current, [x]: F }, v(N.current), w.current && w.current.dispatch({ effects: he.of(null) }); }, []); T.useEffect(() => { typeof n.catalogs == "function" && Promise.resolve(n.catalogs()).then((x) => { h.current = (x || []).map( (F) => F.name.toLowerCase() ); }).catch(() => { }); }, [n]); const R = async (x) => { const F = x.matchBefore(/[\.\w]*/), t = await n.nodes(F.text); c((a) => { const g = { ...a }; let p = !1; for (const I of t) g[I] === void 0 && (g[I] = [], p = !0); return p ? g : a; }); }, J = (x) => { d.setFieldValue("query", x); }, se = async (x, F) => { for (const t of Object.keys(o)) if (x.includes(t) && o[t].length === 0) { const a = await n.node(t), g = ((a == null ? void 0 : a.columns) || []).map((p) => p.name); c((p) => ({ ...p, [t]: g })); } h.current.length > 0 && (clearTimeout(l.current), l.current = setTimeout( () => ce(x), 600 )), clearTimeout(r.current), r.current = setTimeout(() => ie(x), 700); }, ie = async (x) => { if (typeof n.validateNode != "function") return; const F = d.values || {}; if (!F.type) return; let t; try { t = await n.validateNode( F.type, F.name || "__draft__", F.display_name || "Draft", F.description || "", x ); } catch { return; } const a = (t == null ? void 0 : t.json) || {}, g = a.dependencies || [], p = a.missing_parents || []; _(Array.isArray(a.errors) ? a.errors : []); const I = { ...N.current }; for (const E of g) { const k = typeof E == "string" ? E : E == null ? void 0 : E.name; if (!k) continue; const O = typeof E == "object" ? E.type : void 0, j = typeof E == "object" ? E.status : void 0, u = j && j !== "valid" ? "warning" : "valid"; I[k] = { kind: u, refType: O || "node", node: typeof E == "object" ? E : void 0, message: u === "warning" ? `Node \`${k}\` is ${j}` : void 0 }; } for (const E of p) { const k = typeof E == "string" ? E : E == null ? void 0 : E.name; k && (I[k] = { kind: "invalid", refType: "node", message: `Node \`${k}\` not found` }); } N.current = I, v(I), w.current && w.current.dispatch({ effects: he.of(null) }); }, ce = async (x) => { const F = et(x, h.current); for (const [t, a, g] of F) { const p = `${t}.${a}.${g}`; if (m.current.has(p)) continue; if (o[p] !== void 0) { m.current.add(p), D(p, { kind: "valid" }); continue; } m.current.add(p), D(p, { kind: "registering" }); let I; try { I = await n.registerTable( t, a, g, "" ); } catch (O) { m.current.delete(p), D(p, { kind: "invalid", message: (O == null ? void 0 : O.message) || "Failed to register table" }); continue; } const { status: E, json: k } = I; if (E === 200 || E === 201) { const O = (k == null ? void 0 : k.name) || p, j = ((k == null ? void 0 : k.columns) || []).map((u) => u.name); c((u) => { const L = { ...u, [O]: j }; return g && g !== O && (L[g] = j), L; }), D(p, { kind: "valid" }); } else E === 409 ? D(p, { kind: "valid" }) : (m.current.delete(p), D(p, { kind: "invalid", message: (k == null ? void 0 : k.message) || `Registration failed (HTTP ${E})` })); } }, le = T.useMemo( () => nt({ getStatus: (x) => N.current[x], getKnownCatalogs: () => h.current }), [] ), de = T.useMemo( () => rt({ getStatus: (x) => N.current[x], getKnownCatalogs: () => h.current, fetchNodeDetails: (x) => n.node(x).catch(() => null) }), [n] ); return /* @__PURE__ */ y("div", { className: "QueryInput NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "query", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "Query", children: "Query *" }), /* @__PURE__ */ e( U, { type: "textarea", style: { display: "none" }, as: "textarea", name: "query", id: "Query" } ), f.length > 0 && /* @__PURE__ */ e("div", { className: "query-validation-errors", role: "alert", children: f.map((x, F) => /* @__PURE__ */ y("div", { className: "query-validation-error", children: [ /* @__PURE__ */ e("span", { className: "query-validation-error-code", children: ct(x.code) }), /* @__PURE__ */ e("span", { className: "query-validation-error-message", children: x.message }) ] }, F)) }), /* @__PURE__ */ e("div", { role: "button", tabIndex: 0, className: "relative flex bg-[#282a36]", children: /* @__PURE__ */ e( ye, { id: "query", name: "query", extensions: [ s, s.language.data.of({ autocomplete: R }), ...it, le, de ], onCreateEditor: (x) => { w.current = x; }, value: i, placeholder: `SELECT primary_key, measure1, measure2, foreign_key_for_dimension1, foreign_key_for_dimension2 FROM source.source_node WHERE ...`, options: { theme: "default", lineNumbers: !0 }, width: "100%", height: "400px", style: { margin: "0 0 23px 0", flex: 1, fontSize: "110%", textAlign: "left" }, onChange: (x, F) => { J(x), se(x); } } ) }) ] }); }, dt = () => { const n = z(P).DataJunctionAPI, [i, o] = A([]), [c, d] = A([]); return $(() => { (async () => { const l = await n.listMetricMetadata(); d(l.directions), o(l.units); })().catch(console.error); }, [n]), /* @__PURE__ */ y("div", { className: "MetricMetadataFields node-row", children: [ /* @__PURE__ */ y("div", { className: "NodeCreationInput NodeModeInput", children: [ /* @__PURE__ */ e(q, { name: "metric_direction", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "MetricDirection", children: "Direction" }), /* @__PURE__ */ y(U, { as: "select", name: "metric_direction", id: "MetricDirection", children: [ /* @__PURE__ */ e("option", { value: "" }), c.map((s) => /* @__PURE__ */ e("option", { value: s, children: Ue(s) }, s)) ] }) ] }), /* @__PURE__ */ y("div", { className: "NodeCreationInput NodeModeInput", children: [ /* @__PURE__ */ e(q, { name: "metric_unit", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "MetricUnit", children: "Unit" }), /* @__PURE__ */ y(U, { as: "select", name: "metric_unit", id: "MetricUnit", children: [ /* @__PURE__ */ e("option", { value: "" }), i.map((s) => /* @__PURE__ */ e("option", { value: s.name, children: s.label }, s.name)) ] }) ] }), /* @__PURE__ */ y("div", { className: "NodeCreationInput NodeModeInput", children: [ /* @__PURE__ */ e(q, { name: "significant_digits", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "SignificantDigits", children: "Significant Digits" }), /* @__PURE__ */ y(U, { as: "select", name: "significant_digits", id: "SignificantDigits", children: [ /* @__PURE__ */ e("option", { value: "" }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((s) => /* @__PURE__ */ e("option", { value: s, children: s }, s)) ] }) ] }) ] }); }, Ee = ({ defaultValue: n }) => { const i = z(P).DataJunctionAPI, [o, c] = A([]); return $(() => { async function d() { const s = await i.nodesWithType("source"), l = await i.nodesWithType("transform"), r = await i.nodesWithType("dimension"), m = s.concat(l).concat(r); c( m.map((h) => ({ value: h, label: h })) ); } d(); }, [i]), /* @__PURE__ */ y("div", { className: "NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "upstream_node", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "upstream_node", children: "Upstream Node" }), /* @__PURE__ */ y( "p", { className: "field-help-text", style: { fontSize: "0.85em", color: "#666", marginBottom: "8px" }, children: [ "Select a source, transform, or dimension for regular metrics. Leave empty for ", /* @__PURE__ */ e("strong", { children: "derived metrics" }), " that reference other metrics (e.g., ", /* @__PURE__ */ e("code", { children: "namespace.metric_a / namespace.metric_b" }), ")." ] } ), /* @__PURE__ */ e("span", { "data-testid": "select-upstream-node", children: /* @__PURE__ */ e( Q, { className: "", defaultValue: n, selectOptions: o, formikFieldName: "upstream_node", placeholder: "Select Upstream Node (optional for derived metrics)", isMulti: !1, isClearable: !0 } ) }) ] }); }, ke = ({ defaultValue: n }) => { const i = z(P).DataJunctionAPI, { currentUser: o } = Pe(), [, c, d] = ze("owners"), [s, l] = A([]); return $(() => { async function r() { const m = await i.users(); l( m.map((h) => ({ value: h.username, label: h.username })) ); } r(); }, [i]), $(() => { c.value && c.value.length > 0 || (n && n.length > 0 ? d.setValue(n.map((r) => r.value)) : o && d.setValue([o.username])); }, [n, o]), n || o ? /* @__PURE__ */ y("div", { className: "NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "owners", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "Owners", children: "Owners" }), /* @__PURE__ */ e("span", { "data-testid": "select-owner", children: /* @__PURE__ */ e( Q, { className: "", selectOptions: s, formikFieldName: "owners", placeholder: "Select Owners", isMulti: !0 } ) }) ] }) : ""; }, Se = ({ defaultValue: n }) => { const i = z(P).DataJunctionAPI, [o, c] = A([]); return $(() => { (async () => { const s = await i.listTags(); c( s.map((l) => ({ value: l.name, label: l.display_name })) ); })().catch(console.error); }, [i]), /* @__PURE__ */ y("div", { className: "TagsInput NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "tags", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "tags", children: "Tags" }), /* @__PURE__ */ e("span", { "data-testid": "select-tags", children: /* @__PURE__ */ e( Q, { isMulti: !0, selectOptions: o, formikFieldName: "tags", className: "", placeholder: "Choose Tags", defaultValue: n } ) }) ] }); }, mt = ({ initialNamespace: n }) => { const i = z(P).DataJunctionAPI, [o, c] = A([]); return $(() => { (async () => { const s = await i.namespaces(); c( s.map((l) => ({ value: l.namespace, label: l.namespace })) ); })().catch(console.error); }, [i]), /* @__PURE__ */ y("div", { className: "NamespaceInput", children: [ /* @__PURE__ */ e(q, { name: "namespace", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "namespace", children: "Namespace *" }), /* @__PURE__ */ e( Q, { className: "", selectOptions: o, formikFieldName: "namespace", placeholder: "Choose Namespace", defaultValue: { value: n, label: n } } ) ] }); }, ut = ({ message: n }) => /* @__PURE__ */ y("div", { className: "message alert", children: [ /* @__PURE__ */ e(Je, {}), n ] }), pt = () => /* @__PURE__ */ y("div", { className: "DisplayNameInput NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "display_name", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "displayName", children: "Display Name *" }), /* @__PURE__ */ e( U, { type: "text", name: "display_name", id: "displayName", placeholder: "Human readable display name" } ) ] }), ft = () => /* @__PURE__ */ y("div", { className: "DescriptionInput NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "description", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "Description", children: "Description" }), /* @__PURE__ */ e( U, { type: "textarea", as: "textarea", name: "description", id: "Description", placeholder: "Describe your node" } ) ] }), ht = () => /* @__PURE__ */ y("div", { className: "NodeModeInput NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "mode", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "Mode", children: "Mode" }), /* @__PURE__ */ y(U, { as: "select", name: "mode", id: "Mode", children: [ /* @__PURE__ */ e("option", { value: "draft", children: "Draft" }), /* @__PURE__ */ e("option", { value: "published", children: "Published" }) ] }) ] }), Me = ({ defaultValue: n, style: i, className: o = "" }) => { const c = z(P).DataJunctionAPI, { values: d } = W(), [s, l] = A([]); return $(() => { (async () => { if (d.upstream_node) { const m = await c.node(d.upstream_node); l( m.columns.map((h) => ({ value: h.name, label: h.name })) ); } })().catch(console.error); }, [c, d.upstream_node]), /* @__PURE__ */ y("div", { className: "RequiredDimensionsInput CubeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "required_dimensions", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "requiredDimensions", children: "Required Dimensions" }), /* @__PURE__ */ e( Q, { className: o, defaultValue: n, selectOptions: s, formikFieldName: "required_dimensions", placeholder: "Choose Required Dimensions", styles: i, isMulti: !0 } ) ] }); }, De = ({ defaultValue: n, fieldName: i, label: o, labelStyle: c = {}, isMulti: d = !1, isClearable: s = !0 }) => { const l = z(P).DataJunctionAPI, { values: r } = W(), [m, h] = A([]), [f, _] = A(null), b = Ke(() => m && m.length > 0 ? m : [], [m]), v = async () => { try { const { status: N, json: w } = await l.validateNode( r.type, r.name, r.display_name, r.description, r.query ); if (w != null && w.columns) h( w.columns.map((D) => ({ value: D.name, label: D.name })) ), _(null); else { const D = Array.isArray(w == null ? void 0 : w.errors) ? w.errors.map((R) => R.message).filter(Boolean).join(` `) : ""; _(D || (w == null ? void 0 : w.message) || null), h([]); } } catch (N) { console.error("Error fetching columns:", N), _( N.message || "Failed to validate query. Please check your query syntax." ), h([]); } }; return $(() => { v(); }, [r.type, r.name, r.query]), /* @__PURE__ */ y("div", { className: "CubeCreationInput", children: [ /* @__PURE__ */ e(q, { name: i, component: "span" }), /* @__PURE__ */ e("label", { htmlFor: i, style: c, children: o }), f && /* @__PURE__ */ y( "div", { className: "validation-error", style: { color: "#d32f2f", fontSize: "0.875rem", marginBottom: "8px", padding: "8px", backgroundColor: "#ffebee", borderRadius: "4px", border: "1px solid #ef9a9a" }, children: [ /* @__PURE__ */ e("strong", { children: "Validation Error:" }), " ", f ] } ), /* @__PURE__ */ e("span", { "data-testid": `select-${i}`, children: /* @__PURE__ */ e( Q, { className: "", defaultValue: d ? n.map((N) => ({ value: N, label: N })) : n ? { value: n, label: n } : null, selectOptions: b, formikFieldName: i, onFocus: (N) => v(), isMulti: d, isClearable: s } ) }) ] }); }, gt = ({ value: n }) => { const i = W(), o = ge.json(), [c, d] = A(!1); $(() => { if (!n || n === "") { d(!1); return; } const r = typeof n == "string" ? n : JSON.stringify(n, null, 2); try { JSON.parse(r), d(!1); } catch { d(!0); } }, [n]); const s = (r) => r == null ? "" : typeof r == "string" ? r : JSON.stringify(r, null, 2), l = (r) => { if (i.setFieldValue("custom_metadata", r), i.setFieldTouched("custom_metadata", !0), !r || r.trim() === "") d(!1); else try { JSON.parse(r), d(!1); } catch { d(!0); } }; return /* @__PURE__ */ e("div", { className: "QueryInput NodeCreationInput", children: /* @__PURE__ */ y("details", { children: [ /* @__PURE__ */ e("summary", { style: { cursor: "pointer" }, children: /* @__PURE__ */ e( "label", { style: { paddingLeft: "3px", display: "inline-block", pointerEvents: "none" }, children: "Custom Metadata (JSON)" } ) }), /* @__PURE__ */ e(q, { name: "custom_metadata", component: "span" }), /* @__PURE__ */ e( U, { type: "textarea", style: { display: "none" }, as: "textarea", name: "custom_metadata", id: "CustomMetadata", validate: (r) => { if (!(!r || r.trim() === "")) try { const m = JSON.parse(r); if (typeof m == "object" && m !== null && !Array.isArray(m)) { const h = Object.keys(m), f = r.match(/"([^"]+)"\s*:/g); if (f && f.length > h.length) return "Duplicate keys detected"; } return; } catch { return "Invalid JSON format"; } } } ), /* @__PURE__ */ e( "div", { role: "button", tabIndex: 0, className: `relative flex ${c ? "bg-red-900/20" : "bg-[#282a36]"}`, style: { border: c ? "2px solid #ef4444" : "none", borderRadius: "4px", boxShadow: c ? "0 0 0 1px rgba(239, 68, 68, 0.3)" : "none" }, children: /* @__PURE__ */ e( ye, { id: "custom_metadata", name: "custom_metadata", extensions: [o], value: s(n), placeholder: `{ "key": "value" }`, options: { theme: "default", lineNumbers: !0 }, width: "100%", height: "200px", style: { margin: "0 0 23px 0", flex: 1, fontSize: "110%", textAlign: "left" }, onChange: (r, m) => { l(r); } } ) } ) ] }) }); }, B = class B { constructor(i) { this.name = i; } }; fe(B, "Add", new B("add")), fe(B, "Edit", new B("edit")); let S = B; function vt({ extensions: n = {} }) { const i = z(P).DataJunctionAPI, o = We(); let { nodeType: c, initialNamespace: d, name: s } = Ze(); const l = s !== void 0 ? S.Edit : S.Add, [r, m] = A(void 0); $(() => { l === S.Edit && r === !0 && o(`/nodes/${s}`, { replace: !0 }); }, [l, r, s, o]); const h = d || (s ? s.substring(0, s.lastIndexOf(".")) : ""), f = { name: l === S.Edit ? s : "", namespace: l === S.Add ? d : "", display_name: "", query: "", type: c, description: "", primary_key: "", mode: "published", owners: [], custom_metadata: "" }, _ = (t) => { var g; const a = {}; return l === S.Add && !((g = t.display_name) != null && g.trim()) && (a.display_name = "Required"), (!t.name || t.name.endsWith(".") || t.name.startsWith(".") || t.name.includes("..")) && (a.name = "Required"), t.type !== "metric" && !t.query && (a.query = "Required"), a; }, v = [async (t, { setSubmitting: a, setStatus: g }) => { l === S.Add ? await se(t, g).then((p) => { window.scrollTo({ top: 0, left: 0, behavior: "smooth" }), a(!1); }) : await ie(t, g).then((p) => { window.scrollTo({ top: 0, left: 0, behavior: "smooth" }), a(!1); }); }], N = l === S.Add ? /* @__PURE__ */ y("h2", { children: [ "Create", " ", /* @__PURE__ */ e("span", { className: `node_type__${c} node_type_creation_heading`, children: c }) ] }) : /* @__PURE__ */ e("h2", { children: "Edit" }), w = (t) => /* @__PURE__ */ y(K, { children: [ /* @__PURE__ */ y("div", { className: "NodeNameInput NodeCreationInput", children: [ /* @__PURE__ */ e("label", { children: "Name" }), " ", s ] }), /* @__PURE__ */ y("div", { className: "NodeNameInput NodeCreationInput", children: [ /* @__PURE__ */ e("label", { children: "Type" }), " ", t.type ] }) ] }), D = (t) => t.map((a) => a.trim()), R = (t) => { if (!t || t.trim() === "") return null; try { return JSON.parse(t); } catch { return null; } }, J = (t, a) => a && a.trim() !== "" ? `SELECT ${t} FROM ${a}` : `SELECT ${t}`, se = async (t, a) => { const { status: g, json: p } = await i.createNode( c, t.name, t.display_name, t.description, t.type === "metric" ? J(t.aggregate_expression, t.upstream_node) : t.query, t.mode, t.namespace, t.primary_key ? D(t.primary_key) : null, t.metric_direction, t.metric_unit, // Only send required_dimensions if it's set (for metrics) t.required_dimensions && Array.isArray(t.required_dimensions) ? t.required_dimensions : void 0, R(t.custom_metadata) ); g === 200 || g === 201 ? (t.tags && await i.tagsNode(t.name, t.tags), a({ success: /* @__PURE__ */ y(K, { children: [ "Successfully created ", p.type, " node", " ", /* @__PURE__ */ e("a", { href: `/nodes/${p.name}`, children: p.name }), "!" ] }) })) : a({ failure: `${p.message}` }); }, ie = async (t, a) => { const { status: g, json: p } = await i.patchNode( t.name, t.display_name, t.description, t.type === "metric" ? J(t.aggregate_expression, t.upstream_node) : t.query, t.mode, t.primary_key ? D(t.primary_key) : null, t.metric_direction, t.metric_unit, t.significant_digits, // Only send required_dimensions if it's set (for metrics) t.required_dimensions && Array.isArray(t.required_dimensions) ? t.required_dimensions : void 0, t.owners, R(t.custom_metadata) ), I = await i.tagsNode( t.name, t.tags.map((E) => E) ); (g === 200 || g === 201) && I.status === 200 ? a({ success: /* @__PURE__ */ y(K, { children: [ "Successfully updated ", p.type, " node", " ", /* @__PURE__ */ e("a", { href: `/nodes/${p.name}`, children: p.name }), "!" ] }) }) : a({ failure: `${p.message}` }); }, ce = /* @__PURE__ */ y("div", { className: "FullNameInput NodeCreationInput", children: [ /* @__PURE__ */ e(q, { name: "name", component: "span" }), /* @__PURE__ */ e("label", { htmlFor: "FullName", children: "Full Name *" }), /* @__PURE__ */ e(Ge, { type: "text", name: "name" }) ] }), le = (t) => (/* @__PURE__ */ new Set(["transform", "metric", "dimension"])).has(t), de = async (t) => { var p, I, E, k, O, j, u, L, Z, G, Y, X, ee, te; const a = await i.getNodeForEditing(t); if (a === null) return { message: `Node ${t} does not exist` }; const g = { name: a.name, type: a.type.toLowerCase(), display_name: a.current.displayName, description: a.current.description, primary_key: a.current.primaryKey, query: a.current.query, tags: a.tags, mode: a.current.mode.toLowerCase(), owners: a.owners, custom_metadata: a.current.customMetadata }; if (a.type === "METRIC") { const me = a.current.isDerivedMetric, H = a.current.parents.find( (V) => V.type !== "METRIC" ); if (me) { const ne = (a.current.query || "").match(/SELECT\s+(.+)/is), ae = ne ? ne[1].trim() : ((p = a.current.metricMetadata) == null ? void 0 : p.expression) || ""; return { ...g, metric_direction: (E = (I = a.current.metricMetadata) == null ? void 0 : I.direction) == null ? void 0 : E.toLowerCase(), metric_unit: (j = (O = (k = a.current.metricMetadata) == null ? void 0 : k.unit) == null ? void 0 : O.name) == null ? void 0 : j.toLowerCase(), significant_digits: (u = a.current.metricMetadata) == null ? void 0 : u.significantDigits, required_dimensions: a.current.requiredDimensions.map( (ue) => ue.name ), upstream_node: "", // Derived metrics have no upstream node aggregate_expression: ae }; } else return { ...g, metric_direction: (Z = (L = a.current.metricMetadata) == null ? void 0 : L.direction) == null ? void 0 : Z.toLowerCase(), metric_unit: (X = (Y = (G = a.current.metricMetadata) == null ? void 0 : G.unit) == null ? void 0 : Y.name) == null ? void 0 : X.toLowerCase(), significant_digits: (ee = a.current.metricMetadata) == null ? void 0 : ee.significantDigits, required_dimensions: a.current.requiredDimensions.map( (V) => V.name ), upstream_node: (H == null ? void 0 : H.name) || "", aggregate_expression: (te = a.current.metricMetadata) == null ? void 0 : te.expression }; } return g; }, x = (t, a, g) => { if (t.message !== void 0) { a(null), g(`Node ${s} does not exist!`); return; } le(t.type) || (a(null), t.type === "cube" && o(`/nodes/${t.name}/edit-cube`), g(`Node ${s} is of type ${t.type} and cannot be edited`)); }, F = (t, a, g, p, I, E, k, O) => { [ "display_name", "query", "type", "description", "primary_key", "mode", "tags", "aggregate_expression", "upstream_node", "metric_unit", "metric_direction", "significant_digits", "required_dimensions", "owners", "custom_metadata" ].forEach((u) => { if (u === "tags") a( u, t[u].map((L) => L.name) ); else if (u === "owners") a( u, t[u].map((L) => L.username) ); else if (u === "custom_metadata") { const L = t[u] ? JSON.stringify(t[u], null, 2) : ""; a(u, L, !1); } else a(u, t[u] || "", !1); }), g(t), p( /* @__PURE__ */ e( Se, { defaultValue: t.tags.map((u) => ({ value: u.name, label: u.displayName })) } ) ), I( /* @__PURE__ */ e( De, { defaultValue: t.primary_key, fieldName: "primary_key", label: "Primary Key", isMulti: !0 } ) ), t.required_dimensions && k( /* @__PURE__ */ e( Me, { defaultValue: t.required_dimensions.map((u) => ({ value: u, label: u })) } ) ), E( /* @__PURE__ */ e( Ee, { defaultValue: t.upstream_node ? { value: t.upstream_node, label: t.upstream_node } : null } ) ), t.owners && O( /* @__PURE__ */ e( ke, { defaultValue: t.owners.map((u) => ({ value: u.username, label: u.username })) } ) ); }; return l === S.Edit && r !== !1 ? /* @__PURE__ */ e("div", { className: "mid", children: /* @__PURE__ */ e( be, { namespace: h, onReadOnlyChange: m } ) }) : /* @__PURE__ */ y("div", { className: "mid", children: [ /* @__PURE__ */ e( be, { namespace: h, onReadOnlyChange: m } ), /* @__PURE__ */ e("div", { className: "node-builder", children: /* @__PURE__ */ e("div", { className: "node-builder-main", children: /* @__PURE__ */ e( Ve, { initialValues: f, validate: _, validateOnChange: !0, validateOnBlur: !0, onSubmit: async (t, { setSubmitting: a, setStatus: g }) => { try { for (const p of v) await p(t, { setSubmitting: a, setStatus: g }); } catch (p) { console.error("Error in submission", p); } finally { a(!1); } }, children: function(a) { const { isSubmitting: g, status: p, setFieldValue: I, errors: E, touched: k, isValid: O, dirty: j } = a, [u, L] = A([]), [Z, G] = A(null), [Y, X] = A(null), [ee, te] = A(null), [me, H] = A(null), [V, ne] = A(null), [ae, ue] = A(""); return $(() => { (async () => { if (l === S.Edit) { const re = await de(s); x(re, L, ue), F( re, I, L, ne, G, te, X, H ); } })().catch(console.error); }, [I]), /* @__PURE__ */ y(Be, { children: [ Qe(p), l === S.Edit && ae ? /* @__PURE__ */ e(ut, { message: ae }) : /* @__PURE__ */ y(K, { children: [ /* @__PURE__ */ y("div", { className: "node-builder-form-header", children: [ N, /* @__PURE__ */ e(ht, {}) ] }), /* @__PURE__ */ y("div", { className: "node-group", children: [ /* @__PURE__ */ y("div", { className: "node-row", children: [ l === S.Add ? /* @__PURE__ */ e( mt, { initialNamespace: d } ) : w(u), /* @__PURE__ */ e(pt, {}) ] }), l === S.Add ? ce : "", /* @__PURE__ */ e(ft, {}) ] }), /* @__PURE__ */ y("div", { className: "node-group", children: [ c === "metric" || (u == null ? void 0 : u.type) === "metric" ? l === S.Edit ? ee : /* @__PURE__ */ e(Ee, {}) : null, c === "metric" || u.type === "metric" ? /* @__PURE__ */ e( Ye, { djClient: i, value: u.aggregate_expression ? u.aggregate_expression : "" } ) : /* @__PURE__ */ e( lt, { djClient: i, value: u.query ? u.query : "" } ), (c === "metric" || u.type === "metric") && /* @__PURE__ */ e(dt, {}) ] }), /* @__PURE__ */ e("div", { className: "node-group", children: c !== "metric" && u.type !== "metric" ? l === S.Edit ? Z : /* @__PURE__ */ e( De, { defaultValue: [], fieldName: "primary_key", label: "Primary Key", isMulti: !0 } ) : l === S.Edit ? Y : /* @__PURE__ */ e(Me, {}) }), /* @__PURE__ */ e("div", { className: "node-group", children: /* @__PURE__ */ y("div", { className: "node-row", children: [ l === S.Edit ? me : /* @__PURE__ */ e(ke, {}), l === S.Edit ? V : /* @__PURE__ */ e(Se, {}) ] }) }), Object.entries(n).map( ([Ne, re]) => /* @__PURE__ */ e("div", { className: "node-group", children: /* @__PURE__ */ e( re, { node: u, action: l, registerSubmitHandler: (pe, { prepend: Te } = {}) => { v.includes(pe) || (Te ? v.unshift(pe) : v.push(pe)); } } ) }, Ne) ), /* @__PURE__ */ e("div", { className: "node-group node-group-last", children: /* @__PURE__ */ e( gt, { value: u.custom_metadata ? u.custom_metadata : "" } ) }), /* @__PURE__ */ e( "button", { type: "submit", disabled: g || !O, className: `save-node-btn${g ? " save-node-btn--loading" : ""}`, children: g ? /* @__PURE__ */ y(K, { children: [ /* @__PURE__ */ e(He, {}), "Saving..." ] }) : (l === S.Add ? "Create " : "Save ") + (c || "") } ) ] }) ] }); } } ) }) }) ] }); } export { vt as AddEditNodePage }; //# sourceMappingURL=index-BNBtdTGD.js.map