Phase 2 (Milestone 1): Durable analytics artifacts#13
Merged
Conversation
Turns one-shot answers into durable, owned, re-runnable objects: saved
queries with typed parameters, a Postgres result cache/snapshots, charts,
and CSV/JSON/XLSX export.
Backend:
- Models SavedQuery, Chart, ResultSnapshot (connection-scoped, org-keyed,
matching the Phase-1 metadata pattern); migration 005 with a
(sql_hash, taken_at) cache index.
- saved_query_service: type-safe {{param}} rendering, sql_hash, and
cache-first run_saved_query (reuses execute_raw_sql, so the SQL safety
blocklist still applies). RESULT_CACHE_TTL_SECONDS config; openpyxl
[export] extra.
- Endpoints: CRUD + run/clone/export + charts sub-resource.
Frontend (Recharts):
- Saved Queries page, run drawer (param form + cache badge + chart panel),
form modal with params editor, ChartView, SaveQueryModal.
- Client-side CSV/JSON export + "Save query" on the Query result view;
nav item + /saved-queries route.
Verified: 161 backend tests pass; ruff/mypy clean on new files; frontend
lint + build pass; live Docker run confirmed migration 004->5, the
fresh/cached/refresh cache lifecycle, charts, export, clone, and that
param injection is neutralized (table survived).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements Milestone 1 of Phase 2 from
planfull.md— turning one-shot answers into durable, owned, re-runnable objects.{{date_from}},{{region}}), versioned, clone/fork.sha256(final_sql + params + connection_id), TTLRESULT_CACHE_TTL_SECONDSdefault 300 + manual refresh), so re-runs don't re-hit the warehouse.Dashboards (Milestone 2) are intentionally out of scope here.
Backend
SavedQuery,Chart,ResultSnapshot— connection-scoped,organization_id-keyed, matching the Phase-1 metadata pattern (workspace isolation flows through the connection viarequire_connection_read/write).005_durable_artifacts— 3 tables +(sql_hash, taken_at DESC)cache index.saved_query_service.render_sql— type-safe parameter substitution (numbers/booleans inlined, strings/dates escaped + quoted); the rendered SQL still passes throughcheck_sql_safety. Cache-firstrun_saved_queryreusesquery_service.execute_raw_sql./run,/clone,/export?format=csv|json|xlsx, and a charts sub-resource.openpyxladded as the optional[export]extra.Frontend (Recharts added)
SavedQueriesPage(list + Run/Edit/Clone/Delete), run drawer (param form + cache badge + chart panel), form modal with a params editor,ChartView,SaveQueryModal./saved-queriesroute.Notable decisions
organization_idrather than a directworkspace_id— matches the actual Phase-1 implementation rather than the idealized schema inplanfull.md§5.Verification
render_sql/sql_hashtests); ruff + format clean; mypy clean on new files.004→005ran on startup; create → run fresh → cached → refresh-bypass → distinct cache key per param verified; chart create, CSV/JSON/XLSX export, and clone all work.'; DROP TABLE counterparties; --parameter value was neutralized (escaped to a literal and caught by the static blocklist); the table survived with 20 rows. Bad-type params → 422.tsc -b && vite buildsucceeds.🤖 Generated with Claude Code