Skip to content

Optional dataset gate for presenter template injection (partial-appDb builds) - #131

Open
jbrestel wants to merge 2 commits into
masterfrom
dataset-presenter-gate
Open

Optional dataset gate for presenter template injection (partial-appDb builds)#131
jbrestel wants to merge 2 commits into
masterfrom
dataset-presenter-gate

Conversation

@jbrestel

@jbrestel jbrestel commented Aug 10, 2026

Copy link
Copy Markdown
Member

Adds an optional gate to presenter template injection so a site with a partial appDb
can build. Off unless explicitly enabled; no behavior change for full-data or production
builds.

Why

datasetPresenters/*.xml is deliberately a superset of what any one instance has loaded,
and injection ignores that: it builds a DatasetInjector for every presenter it parses.
Harmless on a full-data site. On a dev instance whose appDb holds only a subset, it breaks
the build — presenters for absent datasets have no prop file, so injection fails on props
those datasets would have supplied, or the model later resolves a question whose generated
attribute never appeared. Typical symptoms, all naming a dataset that was never loaded:

  • A datasetInjector for class ... is missing the required property <prop> during presenterInjectTemplates
  • Summary attribute field [...] defined in question [...] is invalid during model load
  • relation "eda.attributevalue_s<hash>_..." does not exist from an injected query

How

Enabled with a JVM system property, forwarded via GUSJVMOPTS:

-Dpresenter.dataset.gate=on                              # appDb from model-config.xml
-Dpresenter.dataset.gate=jdbc:postgresql://host:5432/db  # or name one explicitly

It skips presenters whose dataset (or datasetNamePattern, matched with SQL LIKE
semantics) has no row in apidb.Datasource — the workflow root graph's output, so gating
on it gates on what the workflow actually loaded without the build system needing to know
about ApiCommonWorkflow. Credentials come from the site's model-config.xml, never the
command line.

Deliberate design points worth reviewing:

  • Enabled-but-unreachable is a hard failure. Silently injecting everything would
    reproduce the problem the gate exists to prevent.
  • Skips are always reported — a count on stderr, names in
    $GUS_HOME/lib/wdk/presentersNotLoaded.txt — so a gate that drops too much is visible
    rather than silent.
  • Escape hatch: -Dpresenter.dataset.gate.always=<names> injects a presenter whose
    data is absent. Only works for a dataset declared in ApiCommonDatasets (so
    propertiesFromDatasets generated its prop file) and merely not loaded.

The first commit (Bindu's) is the narrower precursor: it stops getContacts /
getModelReferences validation — and the injector execution that validation triggers —
from running unconditionally for presenters absent from apidb.Datasource.

Tests

TestDatasetGate and TestLikePattern (~300 lines) cover the gating decision and the
LIKE-pattern matching.

Note for review

Master's 8d72fac3 ("Filter dataset injectors by project when a presenter declares more
than one") touches the same addToDatasetInjectorSet method. The cherry-pick auto-merged
cleanly and both filters are present; they narrow the injector set independently, but
that method is the one place these two changes meet.

Provenance

Cherry-picked onto current master from dnaseq-merge-experiments, where this work
happened to land. It is unrelated to the variants/DNA-seq work on that branch — hence a
separate PR. mvn -pl DatasetPresenter -am compile passes.


Related PRs

The variants work spans four repos. These four merge togetherApiCommonModel's record rename is a contract the other three depend on by name:

Coupling worth knowing when sequencing the merges: #1841's customization is resolved by recordClass.fullName, so without #212 it matches nothing and the override silently stops applying — no error, just the default heading back. #310's prefix and #20's separator together produce the ids #212's record class resolves; any two without the third leaves HSSS emitting ids the record class rejects.

Related but independent, cherry-picked onto mastermerge before the four above:

The release note originally in VEuPathDB/ApiCommonWebsite#310 has been deferred (reverted in 965009fdd) because its date attribute was an unresolvable placeholder. Recover the drafted text with git show a039aeee9 in ApiCommonWebsite and re-PR it once the release date is known.

bgajria and others added 2 commits August 10, 2026 18:20
… execution that validation triggers for any DatasetPresenter not found in apidb.Datasource table. Earlier getContacts and getModelReferences were running unconditionally for every presenter in the presenters XML directory, regardless of whether that dataset was actually loaded into the specific database instance being processed, leading to error when Presenter xml has extra entries
datasetPresenters/*.xml is deliberately a superset of what any one instance has
loaded, but injection ignored that: it built a DatasetInjector for every presenter
it parsed, so a presenter whose dataset is absent failed the build. Seen building
PlasmoDB against the unidb_shu_a subset:

  Error: A datasetInjector for class ...ClonedInsertEnds in DatasetPresenter null
         is missing the required property sourceIdField

Five of the ten ClonedInsertEnds presenters have no ApiCommonDatasets declaration
on this branch, so they get no prop file and no sourceIdField.

Commit 1289792 added the same kind of gate to DatasetPresenterSetLoader, but that
keys on getFoundInDb() -- a fact from a different lifecycle phase (presenterWriteToDb)
that TemplatesInjector cannot have, since its only inputs are directories. That gate
stays; it covers the DB-aware phase. This covers the build phase.

The predicate becomes a LoadedDatasetSource, consulted once in
DatasetPresenterSet.createFromPresentersDir so every consumer -- dataset injectors,
contact validation, model references, the loader, anything added later -- inherits
it without knowing it exists. That is what the previous fix lacked, and why it
landed in only one of two paths.

Off by default (AllDatasetsLoaded): no database call, injection identical to before.
A developer opts in with a system property:

  -Dpresenter.dataset.gate=on                              # appDb from model-config.xml
  -Dpresenter.dataset.gate=jdbc:postgresql://host:5432/db  # or name one explicitly
  -Dpresenter.dataset.gate.always=a_RSRC,b_RSRC            # inject these regardless

A property rather than an environment variable because the two build paths do not
share an environment and only a property reaches both: rebuilder discards the
environment on startup but parses --gusjvmopts afterwards, while wb inherits
GUSJVMOPTS from the site's etc/setenv. Both exec the same installed
presenterInjectTemplates, which now forwards GUSJVMOPTS to the JVM -- previously
that variable was set by rebuilder and consumed by nothing.

Matching mirrors the loader's "ds.NAME like ?" exactly, including '_' as a
single-character wildcard, so the two gates can never disagree about membership.
A presenter's datasetNamePattern is resolved the same way the loader resolves it,
and one match is enough since a pattern presenter fans out to many datasets.

Enabled but unable to read apidb.Datasource is a hard failure: degrading to
"inject everything" would reproduce the failure this exists to prevent. Skipped
presenters are always reported -- count to stderr, names to
gus_home/lib/wdk/presentersNotLoaded.txt, because a gate that drops presenters
silently turns a loud build failure into a website that builds green with searches
missing.

Verified on cedar against unidb_shu_a: off => byte-identical pre-change behaviour;
on => 220 datasets read, 3171 presenters skipped, rebuilder reaches SUCCESS and
deploys. 21 unit tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants