diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index b15fd28..df581e7 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -1424,7 +1424,24 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio {entry.operation} - {fmtSlice(entry.slice)} + + {(() => { + const lines = fmtSliceLines(entry.slice) + if (lines.length === 0) return 'โ€”' + // the raw payload is on the title, for the case where the + // rendering has flattened something worth seeing + return ( +
+ {lines.map((l, i) => ( +
+ {lines.length > 1 && {i + 1}.} + {l} +
+ ))} +
+ ) + })()} + `${k} = ${v}`).join(', ') +// A multi-slice operation logs an array of slices, and Object.entries over an +// array yields its indices -- which is where "0 = [object Object]" came from. +// Each slice gets its own line; a date arrives as epoch millis and is useless +// as a number. +function fmtSliceLines(slice) { + if (!slice) return [] + const one = (sl) => Object.entries(sl) + .map(([k, v]) => `${k} = ${fmtSliceValue(v)}`) + .join(' ยท ') + if (!Array.isArray(slice)) return Object.keys(slice).length ? [one(slice)] : [] + + const list = slice.filter(sl => sl && Object.keys(sl).length) + if (list.length <= 1) return list.map(one) + + // A dragged region is one dimension walked across a fixed set of others -- the + // five slices of log 118 differ only in omon. Printing the other four fields + // five times buries the one that actually varies, so pull the shared part out + // and list the varying values on their own line. + const keys = [...new Set(list.flatMap(sl => Object.keys(sl)))] + const varying = keys.filter(k => new Set(list.map(sl => JSON.stringify(sl[k]))).size > 1) + + // Two or more independent axes would lose their pairing if flattened this way, + // so only collapse when a single dimension is doing the varying. + if (varying.length !== 1) return list.map(one) + + const [vk] = varying + const fixed = Object.fromEntries(keys.filter(k => k !== vk).map(k => [k, list[0][k]])) + const values = [...new Set(list.map(sl => fmtSliceValue(sl[vk])))] + return [ + ...(Object.keys(fixed).length ? [one(fixed)] : []), + `${vk} = ${values.join(', ')}`, + ] +} + +function fmtSliceValue(v) { + if (v === null || v === undefined || v === '') return 'โˆ…' + if (typeof v === 'number' && v > 1e11) { + const d = new Date(v) + if (!isNaN(d)) return d.toISOString().slice(0, 10) + } + if (typeof v === 'object') return JSON.stringify(v) + return String(v) } const OP_BADGE = {