UNPKG

datajunction-ui

Version:
1,230 lines (1,229 loc) 92.8 kB
import { jsxs as a, jsx as e, Fragment as S } from "react/jsx-runtime"; import { useRef as sa, useEffect as ca, useCallback as M, useState as f } from "react"; import { Link as Qe } from "react-router-dom"; import { ac as Ve, a1 as ra, a2 as oa } from "./index-n79cxNwC.js"; const Re = { hljs: { display: "block", overflowX: "auto", padding: "0.5em", color: "#383a42", background: "#fafafa" }, "hljs-comment": { color: "#a0a1a7", fontStyle: "italic" }, "hljs-quote": { color: "#a0a1a7", fontStyle: "italic" }, "hljs-doctag": { color: "#a626a4" }, "hljs-keyword": { color: "#a626a4" }, "hljs-formula": { color: "#a626a4" }, "hljs-section": { color: "#e45649" }, "hljs-name": { color: "#e45649" }, "hljs-selector-tag": { color: "#e45649" }, "hljs-deletion": { color: "#e45649" }, "hljs-subst": { color: "#e45649" }, "hljs-literal": { color: "#0184bb" }, "hljs-string": { color: "#50a14f" }, "hljs-regexp": { color: "#50a14f" }, "hljs-addition": { color: "#50a14f" }, "hljs-attribute": { color: "#50a14f" }, "hljs-meta-string": { color: "#50a14f" }, "hljs-built_in": { color: "#c18401" }, "hljs-class .hljs-title": { color: "#c18401" }, "hljs-attr": { color: "#986801" }, "hljs-variable": { color: "#986801" }, "hljs-template-variable": { color: "#986801" }, "hljs-type": { color: "#986801" }, "hljs-selector-class": { color: "#986801" }, "hljs-selector-attr": { color: "#986801" }, "hljs-selector-pseudo": { color: "#986801" }, "hljs-number": { color: "#986801" }, "hljs-symbol": { color: "#4078f2" }, "hljs-bullet": { color: "#4078f2" }, "hljs-link": { color: "#4078f2", textDecoration: "underline" }, "hljs-meta": { color: "#4078f2" }, "hljs-selector-id": { color: "#4078f2" }, "hljs-title": { color: "#4078f2" }, "hljs-emphasis": { fontStyle: "italic" }, "hljs-strong": { fontWeight: "bold" } }; function da(t) { const u = t.split("[")[0], d = u.split("."); return d.length > 1 ? d.slice(0, -1).join(".") : u; } function ma(t) { return (t || []).map((u) => u.split(".").pop()).sort().join(","); } function Ge(t) { if (!t) return null; const u = t.split(" "); if (u.length < 5) return t; const [d, p, N, b, v] = u; if (N === "*" && b === "*" && v === "*") { const g = parseInt(p, 10), k = parseInt(d, 10); if (!isNaN(g) && !isNaN(k)) { const B = g >= 12 ? "pm" : "am", A = g > 12 ? g - 12 : g || 12, U = k.toString().padStart(2, "0"); return `Daily @ ${A}:${U}${B}`; } } if (N === "*" && b === "*" && v !== "*") { const g = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], k = parseInt(v, 10); if (!isNaN(k) && k >= 0 && k <= 6) return `Weekly on ${g[k]}`; } return t; } function ha(t) { var u, d, p; if (!t || ((u = t.workflow_urls) == null ? void 0 : u.length) === 0) return { icon: "○", text: "Not planned", className: "status-not-planned", color: "#94a3b8" }; if (t._isCompatible) { const N = (d = t.grain_columns) == null ? void 0 : d.map((b) => b.split(".").pop()).join(", "); return t.availability || t.status === "active" ? { icon: "✓", text: `Covered (${N})`, className: "status-compatible-materialized", color: "#059669", isCompatible: !0 } : { icon: "◐", text: `Covered (${N})`, className: "status-compatible", color: "#d97706", isCompatible: !0 }; } return t.status === "running" ? { icon: "◉", text: "Running", className: "status-running", color: "#2563eb" } : t.availability || t.status === "active" ? { icon: "●", text: "Materialized", className: "status-materialized", color: "#059669" } : t.workflow_status === "active" ? { icon: "◐", text: "Workflow Active", className: "status-workflow-active", color: "#2563eb" } : t.workflow_status === "paused" ? { icon: "◐", text: "Workflow Paused", className: "status-workflow-paused", color: "#94a3b8" } : ((p = t.workflow_urls) == null ? void 0 : p.length) > 0 ? { icon: "◐", text: "Pending", className: "status-pending", color: "#d97706" } : { icon: "○", text: "Not planned", className: "status-not-planned", color: "#94a3b8" }; } function ua(t) { switch (t == null ? void 0 : t.toUpperCase()) { case "HOUR": return { cron: "0 * * * *", label: "Hourly" }; case "DAY": default: return { cron: "0 6 * * *", label: "Daily at 6:00 AM" }; } } function pa(t) { for (const u of t || []) for (const d of u.grain || []) if (d.split(".").pop().toLowerCase().includes("hour")) return "HOUR"; return "DAY"; } function Ue(t) { if (!t || t === 0) return "0 B"; const u = 1024, d = ["B", "KB", "MB", "GB", "TB", "PB"], p = Math.floor(Math.log(t) / Math.log(u)); return Math.round(t / Math.pow(u, p) * 100) / 100 + " " + d[p]; } function fa(t) { return t > ra ? "critical" : t > oa ? "warning" : "ok"; } function ga(t) { if (!t || !t.sources || t.sources.length === 0) return null; const u = t.sources.some( (N) => N.total_bytes === null || N.total_bytes === void 0 ); let d = "unknown", p = "ℹ️"; return t.total_bytes !== null && t.total_bytes !== void 0 && (d = fa(t.total_bytes), p = d === "critical" ? "⚠️" : d === "warning" ? "⚡" : "✓"), { icon: p, level: d, totalBytes: t.total_bytes, sources: t.sources || [], hasMissingData: u }; } function Na({ onClose: t, onSubmit: u, loading: d }) { const p = (/* @__PURE__ */ new Date()).toISOString().split("T")[0], N = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0], [b, v] = f(N), [g, k] = f(p); return /* @__PURE__ */ e("div", { className: "backfill-modal-overlay", onClick: t, children: /* @__PURE__ */ a("div", { className: "backfill-modal", onClick: (B) => B.stopPropagation(), children: [ /* @__PURE__ */ a("div", { className: "backfill-modal-header", children: [ /* @__PURE__ */ e("h3", { children: "Run Cube Backfill" }), /* @__PURE__ */ e("button", { className: "modal-close", onClick: t, children: "×" }) ] }), /* @__PURE__ */ a("div", { className: "backfill-modal-body", children: [ /* @__PURE__ */ e("p", { className: "backfill-description", children: "Run a backfill for the specified date range:" }), /* @__PURE__ */ a("div", { className: "backfill-date-inputs", children: [ /* @__PURE__ */ a("div", { className: "date-input-group", children: [ /* @__PURE__ */ e("label", { htmlFor: "backfill-start", children: "Start Date" }), /* @__PURE__ */ e( "input", { id: "backfill-start", type: "date", value: b, onChange: (B) => v(B.target.value), disabled: d } ) ] }), /* @__PURE__ */ a("div", { className: "date-input-group", children: [ /* @__PURE__ */ e("label", { htmlFor: "backfill-end", children: "End Date" }), /* @__PURE__ */ e( "input", { id: "backfill-end", type: "date", value: g, onChange: (B) => k(B.target.value), disabled: d } ) ] }) ] }) ] }), /* @__PURE__ */ a("div", { className: "backfill-modal-actions", children: [ /* @__PURE__ */ e( "button", { className: "action-btn action-btn-secondary", onClick: t, disabled: d, children: "Cancel" } ), /* @__PURE__ */ e( "button", { className: "action-btn action-btn-primary", disabled: d || !b, onClick: () => u(b, g || null), children: d ? /* @__PURE__ */ a(S, { children: [ /* @__PURE__ */ e("span", { className: "spinner" }), " Starting..." ] }) : "Start Backfill" } ) ] }) ] }) }); } function Ca({ measuresResult: t, metricsResult: u, selectedMetrics: d, selectedDimensions: p, plannedPreaggs: N = {}, onPlanMaterialization: b, onUpdateConfig: v, onCreateWorkflow: g, onRunBackfill: k, onRunAdhoc: B, onFetchRawSql: A, onSetPartition: U, onRefreshMeasures: w, onFetchNodePartitions: P, materializationError: ee, onClearError: ae, workflowUrls: V = [], onClearWorkflowUrls: ba, loadedCubeName: J = null, // Existing cube name if loaded from preset cubeMaterialization: m = null, // Full cube materialization info {schedule, strategy, lookbackWindow, ...} onUpdateCubeConfig: ue, onRefreshCubeWorkflow: ve, onRunCubeBackfill: ke, onDeactivatePreaggWorkflow: we, onDeactivateCubeWorkflow: Ce }) { var Ee, Oe, He; const _e = M(() => { if (d && d.length > 0) { const n = d[0].split("."); if (n.length > 1) return n.slice(0, -1).join("."); } return "default"; }, [d]), [le, Se] = f({}), [pe, ne] = f(null), [Ye, te] = f(null), [Je, ie] = f(!1), [Ke, fe] = f(!1), [R, K] = f({ strategy: "incremental_time", schedule: "0 6 * * *", lookbackWindow: "1 DAY" }), [De, Te] = f(!1), [Xe, je] = f(!1), [Ze, se] = f({}), [ze, xe] = f(null), [Me, ge] = f({}), [E, Ne] = f({}), [Be, Fe] = f(!1), [s, y] = f({ strategy: "incremental_time", backfillFrom: "", backfillTo: "today", // 'today' or specific date backfillToDate: "", continueAfterBackfill: !0, schedule: "", scheduleType: "auto", // 'auto' or 'custom' lookbackWindow: "1 day", // Druid cube materialization settings enableDruidCube: !0, druidCubeNamespace: "", // Will be initialized with user's namespace druidCubeName: "" // Short name without namespace }), [ce, re] = f(!1), [D, T] = f(null), [Y, O] = f(null), [Pe, H] = f(null), [oe, ea] = f("optimized"), [Q, aa] = f(null), [We, $e] = f(!1), Le = async (l) => { if (ea(l), l === "raw" && !Q && A) { $e(!0); const n = await A(); aa(n), $e(!1); } }, $ = M(() => { const l = (t == null ? void 0 : t.grain_groups) || [], n = /* @__PURE__ */ new Set(); return l.forEach((c) => { c.parent_name && n.add(c.parent_name); }), Array.from(n); }, [t == null ? void 0 : t.grain_groups]), Ie = M(async () => { const l = $(); if (l.length === 0 || !P) return Ne({}), {}; Fe(!0); try { const n = {}, c = l.map(async (r) => { try { const i = await P(r); n[r] = i; } catch (i) { console.error(`Failed to fetch partitions for ${r}:`, i), n[r] = { columns: [], temporalPartitions: [] }; } }); return await Promise.all(c), console.log("[fetchAllNodePartitions] results:", n), Ne(n), n; } catch (n) { return console.error("Failed to fetch node partitions:", n), Ne({}), {}; } finally { Fe(!1); } }, [$, P]), de = M(() => { const l = $(); if (l.length === 0) return !1; const n = l.every((c) => { var i; const r = E[c]; return ((i = r == null ? void 0 : r.temporalPartitions) == null ? void 0 : i.length) > 0; }); return console.log("[hasActualTemporalPartitions]", { parentNodes: l, allNodePartitions: E, allHaveTemporal: n }), n; }, [$, E]); M(() => $().filter((n) => { var r; const c = E[n]; return !((r = c == null ? void 0 : c.temporalPartitions) != null && r.length); }), [$, E]); const la = async () => { const l = (t == null ? void 0 : t.grain_groups) || [], n = pa(l), c = ua(n), r = new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0], i = await Ie(), j = $(), L = j.every( (C) => { var o, x; return ((x = (o = i[C]) == null ? void 0 : o.temporalPartitions) == null ? void 0 : x.length) > 0; } ); console.log( "[openConfigForm] allHaveTemporal:", L, "partitionResults:", i ); const W = /date|time|day|month|year|hour|ds|dt|dateint|timestamp/i, I = {}; j.forEach((C) => { var x, X; const o = i[C]; if (!((x = o == null ? void 0 : o.temporalPartitions) != null && x.length)) { const he = [...((o == null ? void 0 : o.columns) || []).filter((G) => !G.partition)].sort((G, Z) => { const z = W.test(G.name), h = W.test(Z.name); return z && !h ? -1 : !z && h ? 1 : G.name.localeCompare(Z.name); }); I[C] = { column: ((X = he[0]) == null ? void 0 : X.name) || "", granularity: "day", format: "yyyyMMdd" }; } }), se(I), ge({}), y({ strategy: L ? "incremental_time" : "full", runBackfill: !0, // Option to skip backfill backfillFrom: r, backfillTo: "today", backfillToDate: "", continueAfterBackfill: !0, schedule: c.cron, scheduleType: "auto", lookbackWindow: "1 day", _recommendedSchedule: c, // Store for display _granularity: n, // Druid cube settings enableDruidCube: !0, druidCubeNamespace: _e(), // Default to first metric's namespace druidCubeName: "" // Short name without namespace }), ne("__all__"); }, na = (l, n) => { y({ strategy: n.strategy || "incremental_time", backfillFrom: "", backfillTo: "today", backfillToDate: "", continueAfterBackfill: !0, schedule: n.schedule || "", scheduleType: n.schedule ? "custom" : "auto", lookbackWindow: n.lookback_window || "" }), te(l); }, ta = (l) => { Se((n) => ({ ...n, [l]: !n[l] })); }; if (!(d != null && d.length) || !(p != null && p.length)) return /* @__PURE__ */ e("div", { className: "details-panel details-panel-empty", children: /* @__PURE__ */ a("div", { className: "empty-hint", children: [ /* @__PURE__ */ e("div", { className: "empty-icon", children: "⊞" }), /* @__PURE__ */ e("h4", { children: "Query Planner" }), /* @__PURE__ */ e("p", { children: "Select metrics and dimensions from the left panel to see the generated SQL and pre-aggregation plan" }) ] }) }); if (!t || !u) return /* @__PURE__ */ e("div", { className: "details-panel details-panel-empty", children: /* @__PURE__ */ a("div", { className: "empty-hint", children: [ /* @__PURE__ */ e("div", { className: "loading-spinner" }), /* @__PURE__ */ e("p", { children: "Building query plan..." }) ] }) }); const me = t.grain_groups || [], be = t.metric_formulas || [], qe = u.sql || "", Ae = V.length > 0 || Object.values(N).some((l) => { var n; return ((n = l == null ? void 0 : l.workflow_urls) == null ? void 0 : n.length) > 0; }); return /* @__PURE__ */ a("div", { className: "details-panel", children: [ /* @__PURE__ */ a("div", { className: "details-header", children: [ /* @__PURE__ */ e("h2", { className: "details-title", children: "Query Plan" }), /* @__PURE__ */ a("p", { className: "details-full-name", children: [ d.length, " metric", d.length !== 1 ? "s" : "", " ×", " ", p.length, " dimension", p.length !== 1 ? "s" : "" ] }) ] }), ee && /* @__PURE__ */ a("div", { className: "materialization-error global-error", children: [ /* @__PURE__ */ a("div", { className: "error-content", children: [ /* @__PURE__ */ e("span", { className: "error-icon", children: "⚠" }), /* @__PURE__ */ e("span", { className: "error-message", children: ee }) ] }), /* @__PURE__ */ e( "button", { className: "error-dismiss", onClick: ae, "aria-label": "Dismiss error", children: "×" } ) ] }), me.length > 0 && b && (!Ae || pe === "__all__") && /* @__PURE__ */ a("div", { className: "details-section", children: [ !Ae && pe !== "__all__" && /* @__PURE__ */ a("div", { className: "plan-materialization-cta", children: [ /* @__PURE__ */ a("div", { className: "cta-content", children: [ /* @__PURE__ */ e("div", { className: "cta-icon", children: "⚡" }), /* @__PURE__ */ a("div", { className: "cta-text", children: [ /* @__PURE__ */ e("strong", { children: "Ready to materialize?" }), /* @__PURE__ */ e("span", { children: "Configure scheduled materialization for faster queries" }) ] }) ] }), /* @__PURE__ */ e( "button", { className: "action-btn action-btn-primary", type: "button", onClick: la, children: "Configure" } ) ] }), pe === "__all__" && /* @__PURE__ */ a("div", { className: "materialization-config-form section-level-config", children: [ /* @__PURE__ */ a("div", { className: "config-form-header", children: [ /* @__PURE__ */ e("span", { children: "Configure Materialization" }), /* @__PURE__ */ e( "button", { className: "config-close-btn", type: "button", onClick: () => ne(null), children: "×" } ) ] }), /* @__PURE__ */ a("div", { className: "config-form-body", children: [ /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ e("label", { className: "config-form-label", children: "Strategy" }), /* @__PURE__ */ a("div", { className: "config-form-options", children: [ /* @__PURE__ */ a("label", { className: "radio-option", children: [ /* @__PURE__ */ e( "input", { type: "radio", name: "strategy-all", value: "full", checked: s.strategy === "full", onChange: (l) => y((n) => ({ ...n, strategy: l.target.value })) } ), /* @__PURE__ */ e("span", { children: "Full" }) ] }), /* @__PURE__ */ a("label", { className: "radio-option", children: [ /* @__PURE__ */ e( "input", { type: "radio", name: "strategy-all", value: "incremental_time", checked: s.strategy === "incremental_time", onChange: (l) => { y((n) => ({ ...n, strategy: l.target.value })), !de() && !Be && je(!0); } } ), /* @__PURE__ */ e("span", { children: "Incremental" }), s.strategy === "incremental_time" && (Be ? /* @__PURE__ */ e("span", { className: "option-hint", children: "(checking...)" }) : de() ? /* @__PURE__ */ e("span", { className: "partition-badge", children: $().map( (l) => { var n, c, r; return (r = (c = (n = E[l]) == null ? void 0 : n.temporalPartitions) == null ? void 0 : c[0]) == null ? void 0 : r.name; } ).filter(Boolean).join(", ") }) : null) ] }) ] }) ] }), Xe && s.strategy === "incremental_time" && !de() && /* @__PURE__ */ a("div", { className: "partition-setup-form", children: [ /* @__PURE__ */ a("div", { className: "partition-setup-header", children: [ /* @__PURE__ */ e("span", { className: "partition-setup-icon", children: "⚠️" }), /* @__PURE__ */ e("span", { children: "Set up temporal partitions for incremental builds" }) ] }), /* @__PURE__ */ e("div", { className: "partition-setup-body", children: $().map((l) => { var W, I; const n = E[l], c = ((W = n == null ? void 0 : n.temporalPartitions) == null ? void 0 : W.length) > 0, r = Ze[l] || { column: "", granularity: "day", format: "yyyyMMdd" }, i = Me[l], j = ze === l, L = l.split(".").pop(); return c ? /* @__PURE__ */ a( "div", { className: "partition-node-section partition-node-done", children: [ /* @__PURE__ */ e("div", { className: "partition-node-header", children: /* @__PURE__ */ a("span", { className: "partition-node-name", children: [ /* @__PURE__ */ e("span", { className: "partition-node-icon", children: "✓" }), L ] }) }), /* @__PURE__ */ e("div", { className: "partition-node-status", children: /* @__PURE__ */ e("span", { className: "partition-badge", children: (I = n.temporalPartitions[0]) == null ? void 0 : I.name }) }) ] }, l ) : /* @__PURE__ */ a( "div", { className: "partition-node-section", children: [ /* @__PURE__ */ e("div", { className: "partition-node-header", children: /* @__PURE__ */ a("span", { className: "partition-node-name", children: [ /* @__PURE__ */ e("span", { className: "partition-node-icon", children: "📦" }), L ] }) }), i && /* @__PURE__ */ e("div", { className: "partition-setup-error", children: i }), /* @__PURE__ */ a("div", { className: "partition-node-form", children: [ /* @__PURE__ */ a("div", { className: "partition-field", children: [ /* @__PURE__ */ e("label", { children: "Column" }), /* @__PURE__ */ a( "select", { value: r.column, onChange: (C) => se((o) => ({ ...o, [l]: { ...r, column: C.target.value } })), children: [ /* @__PURE__ */ e("option", { value: "", children: "Select..." }), (() => { const C = /date|time|day|month|year|hour|ds|dt|dateint|timestamp/i; return ((n == null ? void 0 : n.columns) || []).filter((o) => !o.partition).map((o) => ({ ...o, isDateLike: C.test( o.name ) })).sort((o, x) => o.isDateLike && !x.isDateLike ? -1 : !o.isDateLike && x.isDateLike ? 1 : o.name.localeCompare(x.name)).map((o) => /* @__PURE__ */ a( "option", { value: o.name, children: [ o.name, o.isDateLike ? " ★" : "" ] }, o.name )); })() ] } ) ] }), /* @__PURE__ */ a("div", { className: "partition-field partition-field-small", children: [ /* @__PURE__ */ e("label", { children: "Granularity" }), /* @__PURE__ */ a( "select", { value: r.granularity, onChange: (C) => se((o) => ({ ...o, [l]: { ...r, granularity: C.target.value } })), children: [ /* @__PURE__ */ e("option", { value: "day", children: "Day" }), /* @__PURE__ */ e("option", { value: "hour", children: "Hour" }), /* @__PURE__ */ e("option", { value: "month", children: "Month" }) ] } ) ] }), /* @__PURE__ */ a("div", { className: "partition-field partition-field-small", children: [ /* @__PURE__ */ e("label", { children: "Format" }), /* @__PURE__ */ e( "input", { type: "text", placeholder: "yyyyMMdd", value: r.format, onChange: (C) => se((o) => ({ ...o, [l]: { ...r, format: C.target.value } })) } ) ] }), /* @__PURE__ */ e( "button", { type: "button", className: "partition-set-btn", disabled: !r.column || j, onClick: async () => { var C; if (r.column) { xe(l), ge((o) => ({ ...o, [l]: null })); try { const o = await U( l, r.column, "temporal", r.format || "yyyyMMdd", r.granularity ); if ((o == null ? void 0 : o.status) >= 400) throw new Error( ((C = o.json) == null ? void 0 : C.message) || "Failed to set partition" ); await Ie(), de() && je(!1); } catch (o) { ge((x) => ({ ...x, [l]: o.message || "Failed to set partition" })); } finally { xe(null); } } }, children: j ? "..." : "Set" } ) ] }) ] }, l ); }) }) ] }), s.strategy === "incremental_time" && /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ a("label", { className: "checkbox-option", children: [ /* @__PURE__ */ e( "input", { type: "checkbox", checked: s.runBackfill, onChange: (l) => y((n) => ({ ...n, runBackfill: l.target.checked })) } ), /* @__PURE__ */ e("span", { children: "Run initial backfill" }) ] }), /* @__PURE__ */ e("span", { className: "config-form-hint", children: "Populate historical data. Uncheck to only set up ongoing materialization." }) ] }), s.strategy === "incremental_time" && s.runBackfill && /* @__PURE__ */ a("div", { className: "config-form-section", children: [ /* @__PURE__ */ e("label", { className: "config-form-section-label", children: "Backfill Date Range" }), /* @__PURE__ */ a("div", { className: "backfill-range", children: [ /* @__PURE__ */ a("div", { className: "backfill-field", children: [ /* @__PURE__ */ e("label", { children: "From" }), /* @__PURE__ */ e( "input", { type: "date", value: s.backfillFrom, onChange: (l) => y((n) => ({ ...n, backfillFrom: l.target.value })) } ) ] }), /* @__PURE__ */ a("div", { className: "backfill-field", children: [ /* @__PURE__ */ e("label", { children: "To" }), /* @__PURE__ */ a( "select", { value: s.backfillTo, onChange: (l) => y((n) => ({ ...n, backfillTo: l.target.value })), children: [ /* @__PURE__ */ e("option", { value: "today", children: "Today" }), /* @__PURE__ */ e("option", { value: "specific", children: "Specific date" }) ] } ), s.backfillTo === "specific" && /* @__PURE__ */ e( "input", { type: "date", value: s.backfillToDate, onChange: (l) => y((n) => ({ ...n, backfillToDate: l.target.value })), style: { marginTop: "6px" } } ) ] }) ] }) ] }), /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ e("label", { className: "config-form-label", children: "Schedule" }), /* @__PURE__ */ a( "select", { className: "config-form-select", value: s.scheduleType, onChange: (l) => { const n = l.target.value; y((c) => { var r; return { ...c, scheduleType: n, schedule: n === "auto" ? ((r = c._recommendedSchedule) == null ? void 0 : r.cron) || "0 6 * * *" : c.schedule }; }); }, children: [ /* @__PURE__ */ a("option", { value: "auto", children: [ ((Ee = s._recommendedSchedule) == null ? void 0 : Ee.label) || "Daily at 6:00 AM", " ", "(recommended)" ] }), /* @__PURE__ */ e("option", { value: "hourly", children: "Hourly" }), /* @__PURE__ */ e("option", { value: "custom", children: "Custom cron..." }) ] } ), s.scheduleType === "custom" && /* @__PURE__ */ e( "input", { type: "text", className: "config-form-input", placeholder: "0 6 * * *", value: s.schedule, onChange: (l) => y((n) => ({ ...n, schedule: l.target.value })), style: { marginTop: "6px" } } ), s.scheduleType === "auto" && /* @__PURE__ */ a("span", { className: "config-form-hint", children: [ "Based on", " ", ((Oe = s._granularity) == null ? void 0 : Oe.toLowerCase()) || "daily", " ", "partition granularity" ] }) ] }), s.strategy === "incremental_time" && /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ e("label", { className: "config-form-label", children: "Lookback Window" }), /* @__PURE__ */ e( "input", { type: "text", className: "config-form-input", placeholder: "1 day", value: s.lookbackWindow, onChange: (l) => y((n) => ({ ...n, lookbackWindow: l.target.value })) } ), /* @__PURE__ */ e("span", { className: "config-form-hint", children: 'For late-arriving data (e.g., "1 day", "3 days")' }) ] }), /* @__PURE__ */ e("div", { className: "config-form-divider", children: /* @__PURE__ */ e("span", { children: "Cube Materialization (Druid)" }) }), /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ a("label", { className: "checkbox-option", children: [ /* @__PURE__ */ e( "input", { type: "checkbox", checked: s.enableDruidCube, onChange: (l) => y((n) => ({ ...n, enableDruidCube: l.target.checked })) } ), "Enable Druid cube materialization" ] }), /* @__PURE__ */ e("span", { className: "config-form-hint", children: "Combines pre-aggs into a single Druid datasource for fast interactive queries" }) ] }), s.enableDruidCube && !J && /* @__PURE__ */ a("div", { className: "config-form-section druid-cube-config", children: [ J ? /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ e("label", { className: "config-form-label", children: "Using Cube" }), /* @__PURE__ */ a("div", { className: "existing-cube-name", children: [ /* @__PURE__ */ e("span", { className: "cube-badge", children: "📦" }), /* @__PURE__ */ e("code", { children: J }) ] }), /* @__PURE__ */ e("span", { className: "config-form-hint", children: "Materialization will be added to this existing cube" }) ] }) : /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ e("label", { className: "config-form-label", children: "Cube Name" }), /* @__PURE__ */ a("div", { className: "cube-name-input-group", children: [ /* @__PURE__ */ e( "input", { type: "text", className: "config-form-input namespace-input", placeholder: "users.myname", value: s.druidCubeNamespace, onChange: (l) => y((n) => ({ ...n, druidCubeNamespace: l.target.value })) } ), /* @__PURE__ */ e("span", { className: "namespace-separator", children: "." }), /* @__PURE__ */ e( "input", { type: "text", className: "config-form-input name-input", placeholder: "my_cube", value: s.druidCubeName, onChange: (l) => y((n) => ({ ...n, druidCubeName: l.target.value })) } ) ] }), /* @__PURE__ */ a("span", { className: "config-form-hint", children: [ "Full name:", " ", /* @__PURE__ */ a("code", { children: [ s.druidCubeNamespace, ".", s.druidCubeName || "my_cube" ] }) ] }) ] }), /* @__PURE__ */ a("div", { className: "druid-cube-preview", children: [ /* @__PURE__ */ e("div", { className: "preview-label", children: "Pre-aggregations to combine:" }), /* @__PURE__ */ e("div", { className: "preview-list", children: ((t == null ? void 0 : t.grain_groups) || []).map((l, n) => { var c; return /* @__PURE__ */ a("div", { className: "preview-item", children: [ /* @__PURE__ */ e("span", { className: "preview-source", children: (c = l.parent_name) == null ? void 0 : c.split(".").pop() }), /* @__PURE__ */ a("span", { className: "preview-grain", children: [ "(", (l.grain || []).map((r) => r.split(".").pop()).join(", "), ")" ] }) ] }, n); }) }), /* @__PURE__ */ a("div", { className: "preview-info", children: [ /* @__PURE__ */ e("span", { className: "info-icon", children: "ℹ️" }), "Pre-aggs will be combined with FULL OUTER JOIN on shared dimensions and ingested to Druid" ] }) ] }) ] }) ] }), /* @__PURE__ */ a("div", { className: "config-form-actions", children: [ /* @__PURE__ */ e( "button", { className: "action-btn action-btn-secondary", type: "button", onClick: () => ne(null), children: "Cancel" } ), /* @__PURE__ */ e( "button", { className: "action-btn action-btn-primary", type: "button", disabled: ce, onClick: async () => { var l; re(!0); try { let n = s.schedule; s.scheduleType === "auto" ? n = ((l = s._recommendedSchedule) == null ? void 0 : l.cron) || "0 6 * * *" : s.scheduleType === "hourly" && (n = "0 * * * *"); let c = null; s.strategy === "incremental_time" && (c = s.backfillTo === "today" ? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] : s.backfillToDate); const r = { strategy: s.strategy, schedule: n, // Always set - we always create workflows lookbackWindow: s.strategy === "incremental_time" ? s.lookbackWindow : null, // Backfill info (only for incremental + runBackfill checked) runBackfill: s.runBackfill, backfillFrom: s.strategy === "incremental_time" && s.runBackfill ? s.backfillFrom : null, backfillTo: s.strategy === "incremental_time" && s.runBackfill ? c : null, // Druid cube config enableDruidCube: s.enableDruidCube, // Only send cube name if creating a new cube (no existing cube loaded) // Combine namespace and name: "users.myname.my_cube" druidCubeName: s.enableDruidCube && s.druidCubeName ? `${s.druidCubeNamespace}.${s.druidCubeName}` : null }; await b(null, r), ne(null), y({ strategy: "incremental_time", runBackfill: !0, backfillFrom: "", backfillTo: "today", backfillToDate: "", continueAfterBackfill: !0, schedule: "", scheduleType: "auto", lookbackWindow: "1 day", enableDruidCube: !0, druidCubeNamespace: _e(), druidCubeName: "" }); } catch (n) { console.error("Failed to plan:", n); } re(!1); }, children: ce ? /* @__PURE__ */ a(S, { children: [ /* @__PURE__ */ e("span", { className: "spinner" }), " Creating..." ] }) : s.enableDruidCube ? "Create Pre-Agg Workflows & Schedule Cube" : s.strategy === "incremental_time" && s.runBackfill ? "Create Workflow & Start Backfill" : "Create Workflow" } ) ] }) ] }) ] }), V.length > 0 && /* @__PURE__ */ a("div", { className: "details-section", children: [ /* @__PURE__ */ e("div", { className: "section-header-row", children: /* @__PURE__ */ a("h3", { className: "section-title", children: [ /* @__PURE__ */ e("span", { className: "section-icon cube-icon", children: "◆" }), "Druid Cube" ] }) }), /* @__PURE__ */ e("div", { className: "preagg-summary-card cube-card", children: Je ? ( /* Edit Cube Config Form - matches pre-agg edit form exactly */ /* @__PURE__ */ a("div", { className: "materialization-config-form", children: [ /* @__PURE__ */ a("div", { className: "config-form-header", children: [ /* @__PURE__ */ e("span", { children: "Edit Materialization Config" }), /* @__PURE__ */ e( "button", { className: "config-close-btn", type: "button", onClick: () => ie(!1), children: "×" } ) ] }), /* @__PURE__ */ a("div", { className: "config-form-body", children: [ /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ e("label", { className: "config-form-label", children: "Strategy" }), /* @__PURE__ */ a("div", { className: "config-form-options", children: [ /* @__PURE__ */ a("label", { className: "radio-option", children: [ /* @__PURE__ */ e( "input", { type: "radio", name: "cube-strategy", value: "full", checked: R.strategy === "full", onChange: (l) => K((n) => ({ ...n, strategy: l.target.value })) } ), /* @__PURE__ */ e("span", { children: "Full" }) ] }), /* @__PURE__ */ a("label", { className: "radio-option", children: [ /* @__PURE__ */ e( "input", { type: "radio", name: "cube-strategy", value: "incremental_time", checked: R.strategy === "incremental_time", onChange: (l) => K((n) => ({ ...n, strategy: l.target.value })) } ), /* @__PURE__ */ e("span", { children: "Incremental (Time)" }) ] }) ] }) ] }), /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ e("label", { className: "config-form-label", children: "Schedule (cron)" }), /* @__PURE__ */ e( "input", { type: "text", className: "config-form-input", placeholder: "0 6 * * * (daily at 6am)", value: R.schedule, onChange: (l) => K((n) => ({ ...n, schedule: l.target.value })) } ) ] }), R.strategy === "incremental_time" && /* @__PURE__ */ a("div", { className: "config-form-row", children: [ /* @__PURE__ */ e("label", { className: "config-form-label", children: "Lookback Window" }), /* @__PURE__ */ e( "input", { type: "text", className: "config-form-input", placeholder: "3 days", value: R.lookbackWindow, onChange: (l) => K((n) => ({ ...n, lookbackWindow: l.target.value })) } ) ] }) ] }), /* @__PURE__ */ a("div", { className: "config-form-actions", children: [ /* @__PURE__ */ e( "button", { className: "action-btn action-btn-secondary", type: "button", onClick: () => ie(!1), children: "Cancel" } ), /* @__PURE__ */ e( "button", { className: "action-btn action-btn-primary", type: "button", disabled: De, onClick: async () => { Te(!0); try { ue && await ue(R), ie(!1); } catch (l) { console.error("Failed to save cube config:", l); } Te(!1); }, children: De ? /* @__PURE__ */ a(S, { children: [ /* @__PURE__ */ e("span", { className: "spinner" }), " Saving..." ] }) : "Save" } ) ] }) ] }) ) : ( /* Cube Summary View - matches pre-agg expandable pattern */ /* @__PURE__ */ a(S, { children: [ /* @__PURE__ */ a("div", { className: "preagg-summary-header", children: [ /* @__PURE__ */ e("span", { className: "preagg-summary-name cube-name", children: (m == null ? void 0 : m.druidDatasource) || (J ? `dj__${J.replace(/\./g, "_")}` : "dj__cube") }), /* @__PURE__ */ e("span", { className: "status-pill status-active", children: "● Active" }) ] }), /* @__PURE__ */ a( "div", { className: "materialization-header clickable", onClick: () => Se((l) => ({ ...l, cube: !l.cube })), children: [ /* @__PURE__ */ a("div", { className: "materialization-status", children: [ /* @__PURE__ */ e( "span", { className: "status-indicator status-materialized", style: { color: "#059669" }, children: "●" } ), /* @__PURE__ */ e("span", { className: "status-text", children: "Workflow active" }), (m == null ? void 0 : m.schedule) && /* @__PURE__ */ a(S, { children: [ /* @__PURE__ */ e("span", { className: "status-separator", children: "|" }), /* @__PURE__ */ e("span", { className: "schedule-summary", children: Ge(m.schedule) }) ] }) ] }), /* @__PURE__ */ e( "button", { className: "expand-toggle", type: "button", "aria-label": le.cube ? "Collapse" : "Expand", children: le.cube ? "▲" : "▼" } ) ] } ), le.cube && /* @__PURE__ */ a("div", { className: "materialization-details", children: [ /* @__PURE__ */ a("div", { className: "materialization-config", children: [ /* @__PURE__ */ a("div", { className: "config-row", children: [ /* @__PURE__ */ e("span", { className: "config-label", children: "Strategy:" }), /* @__PURE__ */ e("span", { className: "config-value", children: (m == null ? void 0 : m.strategy) === "incremental_time" ? "Incremental (Time-based)" : (m == null ? void 0 : m.strategy) === "full" ? "Full" : (m == null ? void 0 : m.strategy) || "Not set" }) ] }), (m == null ? void 0 : m.schedule) && /* @__PURE__ */ a("div", { className: "config-row", children: [ /* @__PURE__ */ e("span", { className: "config-label", children: "Schedule:" }), /* @__PURE__ */ e("span", { className: "config-value config-mono", children: m.schedule }) ] }), (m == null ? void 0 : m.lookbackWindow) && /* @__PURE__ */ a("div", { className: "config-row", children: [ /* @__PURE__ */ e("span", { className: "config-label", children: "Lookback:" }), /* @__PURE__ */ e("span", { className: "config-value", children: m.lookbackWindow }) ] }), /* @__PURE__ */ a("div", { className: "config-row", children: [ /* @__PURE__ */ e("span", { className: "config-label", children: "Dependencies:" }), /* @__PURE__ */ a("span", { className: "config-value", children: [ ((He = m == null ? void 0 : m.preaggTables) == null ? void 0 : He.length) || Object.keys(N).length || me.length, " ", "pre-agg(s)" ] })