Fall back to the source when the member list misses

Autofill stopped filling anything as soon as the member endpoint answered,
because a hit was treated as the only possible answer: if the key was not in
the list, it returned rather than trying the source. An empty list counted as
an answer too, which is the state while a refresh is still running -- so
wiring up master data broke the behaviour it was meant to improve, for
everyone who had not finished building the list yet.

A miss is not an answer. Empty list, or a key the list does not know, both
fall through to the source lookup as before. It costs one request, on a path
that only runs when someone types a value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-17 00:31:54 -04:00
parent 07d92ccd74
commit a3550eabc3

View File

@ -1080,15 +1080,20 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
const meta = colMetaRef.current.find(c => c.cname === col)
const group = meta?.dim_group && dimMembers[meta.dim_group]
// With the member list in hand this is a local lookup. It also answers where
// the source query could not: the source holds every attribute set a part ever
// had, so anything with more than one came back ambiguous and filled nothing.
// A member row is a single definition by construction.
let derived = null
if (group) {
derived = group.members.find(m => m.key_value === value.trim())?.attrs || null
if (!derived) return
} else {
// With the member list in hand this is a local lookup, and it answers where the
// source query could not: the source holds every attribute set a part ever had,
// so anything with more than one came back ambiguous and filled nothing. A
// member row is a single definition by construction.
//
// A miss is not an answer though. The list can be empty (never refreshed, or a
// refresh still running) or simply not contain this key, and in either case the
// source still knows. Falling back costs one request on a path that only runs
// when someone types a value.
let derived = group?.members?.length
? (group.members.find(m => m.key_value === value.trim())?.attrs || null)
: null
if (!derived) {
const res = await fetch(`/api/sources/${sourceId}/lookup?col=${encodeURIComponent(col)}&value=${encodeURIComponent(value)}`)
if (!res.ok) return
derived = await res.json()