diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index 1530b5c..0d2c1ab 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -644,7 +644,9 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio useEffect(() => { refreshLogMeta(versionId) }, [versionId]) // Reordering on the Baseline page takes effect here without a reload: the // expressions are rebuilt and the pivot re-renders. - useEffect(() => { if (tableRef.current) syncOrderExpressions() }, [logMeta, versions, versionId]) + // Re-run when the ordering could have changed on the Baseline page. logMeta is + // the signal, not the source: the function reads the ordering itself. + useEffect(() => { if (tableRef.current) syncOrderExpressions() }, [logMeta, versionId]) useEffect(() => { refreshTags(sourceId) }, [sourceId]) // Stream an Arrow IPC endpoint into one buffer, reporting download progress. @@ -932,20 +934,38 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio console.log(`[pf-order] ${msg}`, extra) } + // Fetches its own inputs rather than reading the `versions` prop and the + // `logMeta` state. + // + // Those are populated asynchronously, and this runs from initViewer — which + // finishes well before them on a large load. It was therefore called with + // versionFound: false and logMetaCount: 0 every time, could never build + // anything, and the effect meant to re-run it once the data arrived never + // fired. Two small queries are worth more than the right timing. async function syncOrderExpressions() { - dbgOrder('enter') const viewer = viewerRef.current - if (!viewer) { dbgOrder('exit: no viewer'); return } - const version = versions.find(v => String(v.id) === String(versionId)) - const wanted = buildOrderExpressions(version?.bucket_order, logMeta) + if (!viewer) return + + let bucketOrder = null + let entries = [] + try { + const [vers, log] = await Promise.all([ + fetch(`/api/sources/${sourceId}/versions`).then(r => r.ok ? r.json() : []), + fetch(`/api/versions/${versionId}/log`).then(r => r.ok ? r.json() : []), + ]) + bucketOrder = (Array.isArray(vers) ? vers : []) + .find(v => String(v.id) === String(versionId))?.bucket_order ?? null + entries = Array.isArray(log) ? log : [] + } catch (err) { + console.error('[pf-order] could not read the ordering', err) + return + } + + const byId = Object.fromEntries(entries.map(e => [e.id, e])) + const wanted = buildOrderExpressions(bucketOrder, byId) dbgOrder('inputs', { - versionId, - versionFound: !!version, - bucket_order: version?.bucket_order ?? null, - logMetaCount: Object.keys(logMeta || {}).length, - seqs: Object.entries(logMeta || {}) - .filter(([, m]) => m.seq != null) - .map(([id, m]) => `${id}=${m.seq}`), + bucket_order: bucketOrder, + seqs: entries.filter(e => e.seq != null).map(e => `${e.tag || e.note}=${e.seq}`), wanted: Object.keys(wanted), })