Drop the measure name from a collapsed column's slice
Clicking a bucket subtotal with the month level collapsed produced
{"customer": "...", "pf_bucket": "04 - Forecast", "smon_e": "sales_usd"} --
a month equal to a measure, matching nothing, so the ledger came back empty
and an operation would have had no rows to act on.
Perspective maps split_by positionally over the column name, and a collapsed
axis has fewer segments than there are split_by levels, so the measure lands
on the first hidden dimension. Both slice paths now drop any == filter whose
value is one of the view's measures; the region path additionally takes the
measure off the end of the column name before mapping, which is the same
error made in our own code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
50a0bb42aa
commit
f94aa4ec99
@ -323,13 +323,23 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
for (let c = c0; c < c1; c++) {
|
for (let c = c0; c < c1; c++) {
|
||||||
const key = userKeys[c]
|
const key = userKeys[c]
|
||||||
if (!key) continue
|
if (!key) continue
|
||||||
|
// A column name is dimension values joined by | with the measure last, and
|
||||||
|
// only as many values as the axis currently shows -- collapse the column
|
||||||
|
// hierarchy and the deeper levels are simply absent. Mapping split_by
|
||||||
|
// positionally over every segment therefore read the measure as a value
|
||||||
|
// for the first collapsed dimension: a bucket subtotal came back as
|
||||||
|
// smon_e = 'sales_usd', which matches no row, so the operation silently
|
||||||
|
// had nothing to act on. Drop the measure, then map over what is left.
|
||||||
|
const segs = key.split('|').slice(0, -1)
|
||||||
const colFilters = splitBy
|
const colFilters = splitBy
|
||||||
|
.slice(0, segs.length)
|
||||||
.map((col, ix) => {
|
.map((col, ix) => {
|
||||||
const v = key.split('|')[ix]
|
const v = segs[ix]
|
||||||
return (v && !META_COL_RE.test(v)) ? [col, '==', v] : null
|
return (v && !META_COL_RE.test(v)) ? [col, '==', v] : null
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
const slice = sliceFromFilters([...base, ...rowFilters, ...colFilters])
|
const slice = sliceFromFilters([...base, ...rowFilters, ...colFilters],
|
||||||
|
(cfg.columns || []).filter(Boolean))
|
||||||
if (!Object.keys(slice).length) continue
|
if (!Object.keys(slice).length) continue
|
||||||
const y = win.start_row + i
|
const y = win.start_row + i
|
||||||
out.push({ slice, area: { x0: c, x1: c, y0: y, y1: y } })
|
out.push({ slice, area: { x0: c, x1: c, y0: y, y1: y } })
|
||||||
@ -770,7 +780,8 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
if (!detail.row) return
|
if (!detail.row) return
|
||||||
const config = await viewer.save()
|
const config = await viewer.save()
|
||||||
if (!(config.group_by || []).length) return
|
if (!(config.group_by || []).length) return
|
||||||
const s = sliceFromFilters((detail.config || {}).filter || [])
|
const s = sliceFromFilters((detail.config || {}).filter || [],
|
||||||
|
(config.columns || []).filter(Boolean))
|
||||||
if (!Object.keys(s).length) return
|
if (!Object.keys(s).length) return
|
||||||
// the CustomEvent carries no modifier flags, so read them off the
|
// the CustomEvent carries no modifier flags, so read them off the
|
||||||
// mousedown that produced it (captured on window below)
|
// mousedown that produced it (captured on window below)
|
||||||
@ -1948,12 +1959,22 @@ function LogCell({ entry, field, placeholder, editing, setEditing, onSave, listI
|
|||||||
const META_COL_RE = /^__(?:ROW_PATH(?:_\d+)?|ID|GROUPING_ID)__$/
|
const META_COL_RE = /^__(?:ROW_PATH(?:_\d+)?|ID|GROUPING_ID)__$/
|
||||||
|
|
||||||
// Perspective encodes a clicked/selected row position as [col, '==', value] triples
|
// Perspective encodes a clicked/selected row position as [col, '==', value] triples
|
||||||
function sliceFromFilters(filters) {
|
// `measures` is the view's `columns` list. Clicking a cell whose column axis is
|
||||||
|
// collapsed makes Perspective emit the measure name as the value of the first
|
||||||
|
// hidden split_by dimension -- a bucket subtotal arrives as
|
||||||
|
// ["smon_e", "==", "sales_usd"] -- because the engine maps split_by positionally
|
||||||
|
// over a column name that no longer has that many segments. Left in, the slice
|
||||||
|
// asks for a month equal to a measure, matches nothing, and the operation
|
||||||
|
// silently has no rows to act on.
|
||||||
|
function sliceFromFilters(filters, measures = []) {
|
||||||
|
const measureSet = new Set(measures)
|
||||||
const s = {}
|
const s = {}
|
||||||
for (const f of filters) {
|
for (const f of filters) {
|
||||||
if (!Array.isArray(f)) continue
|
if (!Array.isArray(f)) continue
|
||||||
const [col, op, val] = f
|
const [col, op, val] = f
|
||||||
if (op === '==' && val != null) s[col] = String(val)
|
if (op !== '==' || val == null) continue
|
||||||
|
if (measureSet.has(String(val))) continue
|
||||||
|
s[col] = String(val)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user