Update Viz
PATCH/v1/viz/:viz_id
Update viz metadata.
ROLE GATE + ACL, not ACL alone. Until 2026-08-25 this route was guarded
ONLY by the folder ACL, so role and the folder ACL were two authorization
systems that disagreed about the product's most valuable object. Measured
on prod: a user the product calls read-only, holding one folder
edit grant, rewrote another user's dashboard description and slug and
flipped viewMode to public — while the adjacent
PUT /viz/{id}/branches/main/files/... refused the same user with
"Your role is read-only." Two adjacent operations on the same resource
cannot disagree about whether the caller may write.
require_creator_or_admin is the same dependency that already guards
folders, dbt, connections and viz FILES; the folder ACL still applies
underneath (it is what decides WHICH viz), so this narrows and never
widens.
Slug handling mirrors the create path: a structurally-invalid slug
(forbidden word / bad charset) is a hard 409 up front, and a collision
with an existing viz on the same account surfaces as a clean 409 via the
VizSlugConflictError raised by the SQL layer's IntegrityError guard.
Previously a combined {name, slug} PATCH onto a taken slug let the
bare IntegrityError propagate → opaque 500 (ticket
viz-create-name-slug-combined-patch-500). Unlike create we do NOT
auto-suffix on update — the caller asked for a specific slug, so a
collision is a 409, never a silent rename.
datasetIds RE-POINTS THE DASHBOARD, and it is validated here for the
first time. The binding is what both query lanes turn into the set of SQL
aliases a tile may read, so changing it changes what the dashboard can see
— the id, the address, the grants, the alerts and the subscriptions all stay
where they are. Create has always refused a binding whose datasets it cannot
read or that are still being prepared; this route accepted any integer and
answered 200, so a typo pointed a live dashboard at nothing and said it had
worked. It now runs the same check, and refuses an EMPTY list outright:
unbinding a dashboard that already renders is never what a re-point meant,
and a dashboard meant to carry hand-written SQL against no dataset is
something to create, not to arrive at by emptying one that works.
Re-sending the binding the dashboard already has is NOT a re-point and is not checked. The app PATCHes whole objects, so the common body restates the ids; validating an echo would make a rename fail because a dataset nobody touched is mid-refresh.
Re-pointing is complete on its own only when the new dataset carries the SAME SQL alias as the old. The generated source names its datasets by alias, never by id, so a new dataset with a different alias needs the source edited and published as well — the binding stops refusing the new alias, it does not rewrite the queries that still name the old one.
Request
Responses
- 200
- 404
- 422
Successful Response
Not found
Validation Error