diff --git a/ui/src/pages/Pivot.jsx b/ui/src/pages/Pivot.jsx index 9675fde..35433d2 100644 --- a/ui/src/pages/Pivot.jsx +++ b/ui/src/pages/Pivot.jsx @@ -74,12 +74,15 @@ function filterRowsByConfig(allRows, filters) { } const LAYOUT_KEY = (source) => `psp_layout_${source}` -const DEFAULT_PLUGIN_CONFIG = { edit_mode: 'SELECT_ROW' } +const DEFAULT_PLUGIN_CONFIG = { edit_mode: 'SELECT_REGION' } + export default function Pivot({ source }) { const viewerRef = useRef() const workerRef = useRef() + const tableRef = useRef() const allRowsRef = useRef([]) + const expandDepthRef = useRef(null) const [status, setStatus] = useState('idle') const [error, setError] = useState('') const [inspectedRows, setInspectedRows] = useState(null) @@ -135,8 +138,9 @@ export default function Pivot({ source }) { if (cancelled) { worker.terminate(); return } workerRef.current = worker - await worker.table(rows, { name: source }) + const table = await worker.table(rows, { name: source }) if (cancelled) return + tableRef.current = table const viewer = viewerRef.current @@ -147,18 +151,42 @@ export default function Pivot({ source }) { const eventFilters = (detail.config || {}).filter || [] const config = await viewer.save() setClickDetail({ row, config, column_names, eventFilters }) - const matched = filterRowsByConfig(allRowsRef.current, eventFilters) - setInspectedRows(matched) + + // Use a Perspective view with the event filters + expressions so computed + // columns (split_by) are evaluated and filtered correctly + try { + const view = await tableRef.current.view({ + filter: eventFilters, + expressions: config.expressions || [], + }) + const data = await view.to_json() + await view.delete() + // Strip expression columns — only show raw source columns + const exprNames = new Set(Object.keys(config.expressions || {})) + const cleaned = data.map(r => + Object.fromEntries(Object.entries(r).filter(([k]) => !exprNames.has(k))) + ) + setInspectedRows(cleaned) + } catch { + setInspectedRows(filterRowsByConfig(allRowsRef.current, eventFilters)) + } }) await viewer.load(worker) + const plugin = await viewer.getPlugin() const savedLayout = localStorage.getItem(LAYOUT_KEY(source)) if (savedLayout) { - await viewer.restore(JSON.parse(savedLayout)) + const parsed = JSON.parse(savedLayout) + await viewer.restore(parsed) + await plugin.restore(parsed.plugin_config || DEFAULT_PLUGIN_CONFIG) + if (parsed.expand_depth != null) await applyExpandDepth(viewer, parsed.expand_depth) } else { await viewer.restore({ table: source, settings: true, plugin_config: DEFAULT_PLUGIN_CONFIG }) + await plugin.restore(DEFAULT_PLUGIN_CONFIG) } + await viewer.flush() + setStatus('ready') } catch (err) { if (!cancelled) { setStatus('error'); setError(err.message) } @@ -169,21 +197,58 @@ export default function Pivot({ source }) { return () => { cancelled = true } }, [source]) + async function applyExpandDepth(viewer, depth) { + if (depth == null) return + const view = await viewer.getView() + await view.set_depth(depth) + const plugin = await viewer.getPlugin() + await plugin.draw(view) + expandDepthRef.current = depth + } + async function applyLayout(layout) { const viewer = viewerRef.current if (!viewer) return await viewer.restore(layout.config) + if (layout.config.plugin_config) { + const plugin = await viewer.getPlugin() + await plugin.restore(layout.config.plugin_config) + } + await applyExpandDepth(viewer, layout.config.expand_depth ?? null) setActiveLayoutId(layout.id) // also persist to localStorage so it survives refresh localStorage.setItem(LAYOUT_KEY(source), JSON.stringify(layout.config)) } + async function captureConfig() { + const viewer = viewerRef.current + if (!viewer) return null + const plugin = await viewer.getPlugin() + const [viewerConfig, pluginConfig] = await Promise.all([viewer.save(), plugin.save()]) + return { ...viewerConfig, plugin_config: pluginConfig, expand_depth: expandDepthRef.current } + } + + async function handleSaveOver() { + const layout = layouts.find(l => l.id === activeLayoutId) + if (!layout) return + const config = await captureConfig() + if (!config) return + try { + const saved = await api.savePivotLayout(source, layout.layout_name, config) + localStorage.setItem(LAYOUT_KEY(source), JSON.stringify(config)) + await loadLayouts() + setActiveLayoutId(saved.id) + flashMsg('Saved!') + } catch (err) { + flashMsg(err.message) + } + } + async function handleSaveAs() { const name = saveAsName.trim() if (!name) return - const viewer = viewerRef.current - if (!viewer) return - const config = await viewer.save() + const config = await captureConfig() + if (!config) return try { const saved = await api.savePivotLayout(source, name, config) localStorage.setItem(LAYOUT_KEY(source), JSON.stringify(config)) @@ -257,6 +322,13 @@ export default function Pivot({ source }) { ))} + {activeLayoutId !== null && !showSaveAs && ( + + )} + {showSaveAs ? (