@quantumwake/terminal-ux-dashboard-components 0.1.8 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +135 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -7
- package/dist/index.d.ts +4 -7
- package/dist/index.js +136 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -44,7 +44,9 @@ function useCapabilities() {
|
|
|
44
44
|
canRefine: !!c.refineDashboard,
|
|
45
45
|
canNew: !!c.newDashboard,
|
|
46
46
|
canAddPanel: !!c.addPanel,
|
|
47
|
-
canEditPanels: !!c.removePanel
|
|
47
|
+
canEditPanels: !!c.removePanel,
|
|
48
|
+
// Studio can recompute + persist a panel's precomputed result.
|
|
49
|
+
canRefresh: !!c.persistPanelData
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -1346,6 +1348,7 @@ var ChartPreview = ({
|
|
|
1346
1348
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "Select a chart type" });
|
|
1347
1349
|
}
|
|
1348
1350
|
};
|
|
1351
|
+
var MAX_PRECOMPUTE_ROWS = 5e3;
|
|
1349
1352
|
function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
1350
1353
|
const { theme, runQuery } = useDashboard();
|
|
1351
1354
|
const runQueryRef = react.useRef(runQuery);
|
|
@@ -1474,6 +1477,10 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1474
1477
|
config.chartType = chartType;
|
|
1475
1478
|
if (filters.length) config.filters = filters;
|
|
1476
1479
|
if (yFields.length) config.yFields = yFields.map((f) => ({ name: f.name, agg: f.agg }));
|
|
1480
|
+
if (sqlRows && sqlRows.length <= MAX_PRECOMPUTE_ROWS) {
|
|
1481
|
+
config.data = sqlRows;
|
|
1482
|
+
config.refreshedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1483
|
+
}
|
|
1477
1484
|
}
|
|
1478
1485
|
config.style = style;
|
|
1479
1486
|
onSave({
|
|
@@ -1677,76 +1684,167 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1677
1684
|
function ViewLoading() {
|
|
1678
1685
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
|
|
1679
1686
|
}
|
|
1687
|
+
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap"]);
|
|
1688
|
+
function panelToChartConfig(chartType, config) {
|
|
1689
|
+
const cfg = { chartType, xFields: [], yFields: [], filters: config.filters || [] };
|
|
1690
|
+
switch (chartType) {
|
|
1691
|
+
case "bar":
|
|
1692
|
+
case "grouped-bar":
|
|
1693
|
+
if (config.group) cfg.xFields = [{ name: config.group }];
|
|
1694
|
+
cfg.yFields = config.yFields?.length ? config.yFields : config.value ? [{ name: config.value, agg: config.agg }] : [];
|
|
1695
|
+
break;
|
|
1696
|
+
case "pie":
|
|
1697
|
+
if (config.group) cfg.xFields = [{ name: config.group }];
|
|
1698
|
+
break;
|
|
1699
|
+
case "line":
|
|
1700
|
+
case "scatter":
|
|
1701
|
+
if (config.x) cfg.xFields = [{ name: config.x }];
|
|
1702
|
+
if (config.y) cfg.yFields = [{ name: config.y }];
|
|
1703
|
+
break;
|
|
1704
|
+
case "heatmap":
|
|
1705
|
+
if (config.row) cfg.xFields = [{ name: config.row }];
|
|
1706
|
+
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
1707
|
+
if (config.value) cfg.valueField = { name: config.value, agg: config.agg };
|
|
1708
|
+
break;
|
|
1709
|
+
}
|
|
1710
|
+
return cfg;
|
|
1711
|
+
}
|
|
1712
|
+
function panelSql(chartType, config) {
|
|
1713
|
+
if (config.sql) return config.sql;
|
|
1714
|
+
if (SQL_CHART_TYPES.has(chartType)) return buildChartSQL(panelToChartConfig(chartType, config)) || "";
|
|
1715
|
+
return "";
|
|
1716
|
+
}
|
|
1680
1717
|
function SqlPanel({ panel }) {
|
|
1681
|
-
const { runQuery } = useDashboard();
|
|
1718
|
+
const { runQuery, persistPanelData } = useDashboard();
|
|
1719
|
+
const { canRefresh } = useCapabilities();
|
|
1682
1720
|
const runQueryRef = react.useRef(runQuery);
|
|
1683
1721
|
runQueryRef.current = runQuery;
|
|
1684
1722
|
const config = panel.config || {};
|
|
1685
1723
|
const chartType = config.chartType || panel.type;
|
|
1686
|
-
const
|
|
1724
|
+
const sql = panelSql(chartType, config);
|
|
1725
|
+
const precomputed = config.data;
|
|
1726
|
+
const [rows, setRows] = react.useState(precomputed ?? null);
|
|
1687
1727
|
const [error, setError] = react.useState(null);
|
|
1688
|
-
const [loading, setLoading] = react.useState(
|
|
1728
|
+
const [loading, setLoading] = react.useState(!precomputed && canRefresh);
|
|
1729
|
+
const [refreshing, setRefreshing] = react.useState(false);
|
|
1689
1730
|
react.useEffect(() => {
|
|
1731
|
+
if (precomputed) {
|
|
1732
|
+
setRows(precomputed);
|
|
1733
|
+
setLoading(false);
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
if (!canRefresh) {
|
|
1737
|
+
setLoading(false);
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1690
1740
|
let cancelled = false;
|
|
1691
1741
|
setLoading(true);
|
|
1692
1742
|
setError(null);
|
|
1693
|
-
const sql = config.sql || "";
|
|
1694
1743
|
runQueryRef.current(sql).then(
|
|
1695
1744
|
(res) => {
|
|
1696
|
-
if (cancelled)
|
|
1697
|
-
|
|
1698
|
-
|
|
1745
|
+
if (!cancelled) {
|
|
1746
|
+
setLoading(false);
|
|
1747
|
+
setRows(res?.rows || []);
|
|
1748
|
+
}
|
|
1699
1749
|
},
|
|
1700
1750
|
(err) => {
|
|
1701
|
-
if (cancelled)
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1751
|
+
if (!cancelled) {
|
|
1752
|
+
setLoading(false);
|
|
1753
|
+
setRows(null);
|
|
1754
|
+
setError(err instanceof Error ? err.message : String(err) || "Query failed");
|
|
1755
|
+
}
|
|
1705
1756
|
}
|
|
1706
1757
|
);
|
|
1707
1758
|
return () => {
|
|
1708
1759
|
cancelled = true;
|
|
1709
1760
|
};
|
|
1710
|
-
}, [
|
|
1761
|
+
}, [sql, canRefresh]);
|
|
1762
|
+
const refresh = react.useCallback(async () => {
|
|
1763
|
+
setRefreshing(true);
|
|
1764
|
+
setError(null);
|
|
1765
|
+
try {
|
|
1766
|
+
const res = await runQueryRef.current(sql);
|
|
1767
|
+
const r = res?.rows || [];
|
|
1768
|
+
setRows(r);
|
|
1769
|
+
persistPanelData?.(panel.id, r, (/* @__PURE__ */ new Date()).toISOString());
|
|
1770
|
+
} catch (err) {
|
|
1771
|
+
setError(err instanceof Error ? err.message : String(err) || "Query failed");
|
|
1772
|
+
} finally {
|
|
1773
|
+
setRefreshing(false);
|
|
1774
|
+
}
|
|
1775
|
+
}, [sql, panel.id, persistPanelData]);
|
|
1776
|
+
const refreshBtn = canRefresh ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1777
|
+
"button",
|
|
1778
|
+
{
|
|
1779
|
+
onClick: refresh,
|
|
1780
|
+
disabled: refreshing,
|
|
1781
|
+
title: config.refreshedAt ? `Last computed ${new Date(config.refreshedAt).toLocaleString()} \u2014 click to refresh` : "Compute & save this chart\u2019s data",
|
|
1782
|
+
className: "absolute top-1 right-1 z-10 p-1 bg-midnight-elevated/80 border border-midnight-border text-midnight-text-muted hover:text-midnight-accent transition-colors",
|
|
1783
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `w-3 h-3 ${refreshing ? "animate-spin" : ""}` })
|
|
1784
|
+
}
|
|
1785
|
+
) : null;
|
|
1711
1786
|
if (loading && !rows) {
|
|
1712
1787
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Running\u2026" });
|
|
1713
1788
|
}
|
|
1789
|
+
if (!rows && !error && !canRefresh) {
|
|
1790
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-1 px-2 text-center text-xs text-midnight-text-muted", children: [
|
|
1791
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-4 h-4" }),
|
|
1792
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Chart data not available" }),
|
|
1793
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-midnight-text-subdued", children: "Download the dataset to view this chart." })
|
|
1794
|
+
] });
|
|
1795
|
+
}
|
|
1714
1796
|
if (error) {
|
|
1715
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1797
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center h-full px-2 text-center text-xs text-red-400", children: [
|
|
1798
|
+
refreshBtn,
|
|
1799
|
+
error
|
|
1800
|
+
] });
|
|
1716
1801
|
}
|
|
1717
1802
|
if (!rows) return null;
|
|
1803
|
+
let chart;
|
|
1718
1804
|
if (chartType === "metric") {
|
|
1719
1805
|
const first = rows[0];
|
|
1720
1806
|
const v = first ? Object.values(first)[0] : 0;
|
|
1721
|
-
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1807
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1808
|
+
} else {
|
|
1809
|
+
const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
|
|
1810
|
+
switch (chartType) {
|
|
1811
|
+
case "bar":
|
|
1812
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(BarView, { data: shaped, groupColumn: config.group || "", valueColumn: config.value || "", style: config.style });
|
|
1813
|
+
break;
|
|
1814
|
+
case "grouped-bar": {
|
|
1815
|
+
const gb = shaped;
|
|
1816
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(GroupedBarChart, { data: gb.data, keys: gb.keys, yFields: config.yFields || [], style: config.style });
|
|
1817
|
+
break;
|
|
1818
|
+
}
|
|
1819
|
+
case "pie":
|
|
1820
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(PieView, { data: shaped, groupColumn: config.group || "", style: config.style });
|
|
1821
|
+
break;
|
|
1822
|
+
case "line":
|
|
1823
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(LineView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
|
|
1824
|
+
break;
|
|
1825
|
+
case "scatter":
|
|
1826
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
|
|
1827
|
+
break;
|
|
1828
|
+
case "heatmap":
|
|
1829
|
+
chart = shaped.length ? /* @__PURE__ */ jsxRuntime.jsx(HeatmapView, { data: shaped, rowColumn: config.row || "", colColumn: config.col || "", rowOrder: config.rowOrder, colOrder: config.colOrder, marginAgg: config.marginAgg, showTotals: config.showTotals, style: config.style }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
|
|
1830
|
+
break;
|
|
1831
|
+
default:
|
|
1832
|
+
chart = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1833
|
+
"Unsupported SQL chart: ",
|
|
1834
|
+
chartType
|
|
1835
|
+
] });
|
|
1730
1836
|
}
|
|
1731
|
-
case "pie":
|
|
1732
|
-
return /* @__PURE__ */ jsxRuntime.jsx(PieView, { data: shaped, groupColumn: config.group || "", style: config.style });
|
|
1733
|
-
case "line":
|
|
1734
|
-
return /* @__PURE__ */ jsxRuntime.jsx(LineView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
|
|
1735
|
-
case "scatter":
|
|
1736
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
|
|
1737
|
-
case "heatmap":
|
|
1738
|
-
return shaped.length ? /* @__PURE__ */ jsxRuntime.jsx(HeatmapView, { data: shaped, rowColumn: config.row || "", colColumn: config.col || "", rowOrder: config.rowOrder, colOrder: config.colOrder, marginAgg: config.marginAgg, showTotals: config.showTotals, style: config.style }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
|
|
1739
|
-
default:
|
|
1740
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1741
|
-
"Unsupported SQL chart: ",
|
|
1742
|
-
chartType
|
|
1743
|
-
] });
|
|
1744
1837
|
}
|
|
1838
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full w-full", children: [
|
|
1839
|
+
refreshBtn,
|
|
1840
|
+
chart
|
|
1841
|
+
] });
|
|
1745
1842
|
}
|
|
1746
1843
|
function PanelContent({ panel, records, columns }) {
|
|
1747
1844
|
const { type } = panel;
|
|
1748
1845
|
const config = panel.config || {};
|
|
1749
|
-
|
|
1846
|
+
const effType = config.chartType || type;
|
|
1847
|
+
if (config.sql || SQL_CHART_TYPES.has(effType)) {
|
|
1750
1848
|
return /* @__PURE__ */ jsxRuntime.jsx(SqlPanel, { panel });
|
|
1751
1849
|
}
|
|
1752
1850
|
if (!records?.length && type !== "insight") {
|