Stacked on #48305.
## What
PR 3 of the stacked SQL-editor query-source series (Database vs Logs).
Stacked on the PR 2 branch `charislam/log-sql-content-shape`.
Turns `SnippetWithContent` into a discriminated union on `type` and
makes all snippet writes source-aware:
- `data/content/sql-folders-query.ts`: `SnippetWithContent` is now `{
type: 'sql'; content?: SqlSnippets.Content } | { type: 'log_sql';
content?: LogSqlSnippets.Content } | { type: 'report'; content?: never
}`. `report` is kept (the content endpoints' wire type carries it) but
has no SQL content — its body is `Dashboards.Content`, loaded through
the separate `Content` union.
- `setSql` brands per type (`untrustedLogSql` vs `untrustedSql`).
- `buildUpsertPayload` persists `snippet.type` (no longer hardcoded
`'sql'`).
- `createSqlSnippetSkeletonV2({ source })` emits the matching type +
content shape with the `as any` cast removed.
- New `components/interfaces/SQLEditor/querySource.ts`:
`SqlSnippetSource` + `getSnippetSource`.
- `seedSnippet` test helper gains a `source` arg.
- New `remapWireSnippet` boundary helper in `content-remap.ts`
concentrates the single wire->domain assertion, so `content-id-query` /
`content-upsert-mutation` call sites are cast-free (no `as unknown as`).
- Collateral: query result types aligned to the union; `updateSnippet`
no longer accepts `type` (source is immutable); db-only editor read
paths narrow away `log_sql`.
## Why
Impossible-states-impossible typing: a snippet's brand follows its
content type, so logs SQL and database SQL can never cross execution
paths. No behavior change for existing database snippets.
## Testing
- \`pnpm typecheck\` — clean
- \`pnpm --filter studio run lint:ratchet\` — no new warnings
- \`pnpm test:studio\` (data/content, SQLEditor, state/sql-editor) —
passing, including new tests for \`getSnippetSource\`, source-aware
\`setSql\`, type-aware \`buildUpsertPayload\`, and both skeleton shapes.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added source-aware creation for SQL editor snippets, including
log-based SQL snippets.
* Introduced backend source mapping so log snippets are treated as
log_sql.
* **Bug Fixes**
* Improved SQL retrieval/prettification so log snippets no longer use
the wrong fallback content.
* Ensured log snippets are sanitized and preserve correct type, content,
identifiers, and statuses during save/upsert flows.
* **Tests**
* Expanded unit and integration coverage for log snippet creation,
source mapping, editing, prettification, and upsert payloads.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What
PR **2 of 9** in the SQL-editor query-source (Database vs Logs) stack.
**Base:** `charislam/snippet-source-typing` (#48301) — this is a stacked
PR; review/merge that one first.
Client-side rename only — **the wire format is unchanged** (the platform
API still stores and returns `content.sql`). This moves the frontend
`LogSqlSnippets.Content` field to the branded `unchecked_sql`, matching
`SqlSnippets.Content`, and hardens the remap boundary so the rename
can't silently drop saved query text.
## Changes
- **`types/userContent.ts`** — `LogSqlSnippets.Content`'s plain `sql:
string` becomes `unchecked_sql: UntrustedLogSqlFragment` (the brand
added in PR 1). Shape kept minimal: `{ content_id, unchecked_sql,
schema_version }`.
- **`data/content/content-remap.ts`** — extend
`remapSqlContentField`/`unmapSqlContentField` to `log_sql`, branding
**per type** (`untrustedLogSql` for logs, `untrustedSql` for database)
and never mixing brands. **Defensive unmap**: content missing
`unchecked_sql` is never clobbered with `sql: undefined`; a residual raw
`sql` field (a missed save-path rename) throws in development to surface
the bug loudly, while production no-ops safely.
- **Legacy Logs Explorer consumers** updated to the branded field: the
explorer save/update paths, `SavedQueriesItem`, `RecentQueriesItem`, and
the recent-queries page.
- **Two db-only write sites** that leaned on
`LogSqlSnippets.Content.sql`: `EditorPanel` now saves `unchecked_sql`,
and `MoveQueryModal` switches to the SQL-editor-specific
`getSqlSnippetById` so its content is typed as `SqlSnippets.Content` —
no narrowing or casting.
## Tests
- **content-remap**: `log_sql` remap/unmap round-trip with the logs
brand; the defensive-unmap no-op (prod) and dev throw.
- **content-upsert-mutation**: a `log_sql` payload reaches the wire as a
plain `content.sql` and the response remaps back to `unchecked_sql` (the
data-loss-critical round-trip shared by both explorer save-new and
`SavedQueriesItem` update).
## Verification
- `pnpm --filter studio run typecheck` ✓
- `pnpm --filter studio run lint:ratchet` ✓ (no new warnings)
- `pnpm test:studio` for `data/content` + `Settings/Logs` — 139 passing
✓
- Prettier ✓
Nothing is user-visible yet — logs snippet entry points arrive later in
the stack behind the `sqlEditorLogsSource` flag.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Improved handling of saved and recent log queries across the SQL
editor and Logs Explorer.
- Log SQL now uses `unchecked_sql` (branded as untrusted) consistently
when creating, editing, moving, and reopening queries, with correct
remapping to/from the API boundary.
- Fixed saved-query update payloads to preserve the right query content
and omit legacy fields.
- **Tests**
- Added/expanded Vitest coverage for saved log query editing, recent-log
normalization, and `log_sql` remap/upsert request/response behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Snippet content was wiped blank after a move or rename (until dashboard
refreshed) because it depended on the API returning the new content, but
the API returns under the `content` field, not the `unchecked_sql` field
that is expected. Added a `remapSqlContentField` remap to fix.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved the saved content response so snippet fields are mapped
consistently before being returned.
* Kept the saved status unchanged while updating the returned data shape
for better accuracy.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What
PR 3 of a stacked refactor of the SQL editor snippet state. Replaces the
two overlapping pieces of snippet lifecycle state — the `savingStates`
map (`IDLE|UPDATING|UPDATING_FAILED`) and the `isNotSavedInDatabaseYet`
boolean — with a single `SnippetStatus` enum.
## Status is attached at the data layer (never absent)
- `SnippetStatus` + `SnippetWithContent` now live in `data/content`. The
snippet queries attach `status: 'saved'` via a typed `withSavedStatus()`
helper, and `upsertContent` returns `SnippetWithContent` so move/rename
responses carry status too.
- A SQL-typed `getSqlSnippetById`/`useSqlSnippetByIdQuery` returns
`SnippetWithContent` (the generic `useContentIdQuery` stays for Reports,
which use it). `[id].tsx` loads content with **no casting**.
- `'new'` is attached on local creation (`createSqlSnippetSkeletonV2`).
## Behavior
Behavior-preserving for the existing auto-save flow (faithful mapping of
both old fields, including the replication-lag swallow). One incidental
fix: the read-only/saving indicator now also covers a brand-new
snippet's first save (previously only re-saves of persisted snippets had
distinct saving/failed states in some paths).
## Tests
New `sql-editor-lifecycle.test.ts` (29 tests) covering every predicate
and transition; existing rules tests updated. `pnpm --filter studio
typecheck` clean; 52 state/sql-editor unit tests pass.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Refactor**
* Restructured SQL snippet persistence tracking, replacing boolean flags
with a comprehensive status system for clearer visibility into save
progress.
* Enhanced saving indicator UI to reflect accurate snippet save states.
* **Tests**
* Added test coverage for snippet persistence state transitions and
lifecycle scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
Deleting a custom report waited for the API round trip before updating
the UI. The confirmation modal showed a loading spinner, the report
stayed visible in the sidebar until the request resolved, and the
interaction felt sluggish.
## Fix
The delete now applies optimistically. On confirm, the report is removed
from the sidebar immediately and the user is navigated away. The actual
delete runs in the background. If it fails, the cached list is rolled
back to its previous state and an error toast is shown.
The optimistic behavior lives inside `useContentDeleteMutation` (via
`onMutate` snapshot + `onError` rollback), so any current or future
caller of that hook gets it for free, no per-call wiring required.
## How to test
- Open a project with at least one custom report
- Click the kebab menu on a report and choose Delete report, then
confirm
- Expected result: the report disappears from the sidebar instantly and
a success toast appears
- To test rollback: throttle/offline the network or force the delete
endpoint to fail, then delete again
- Expected result: the report reappears in the sidebar and an error
toast is shown
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Improvements**
* Deletion flows now provide explicit loading, success and error
feedback; UI updates immediately on delete and will restore if the
action fails.
* **Removals**
* Removed the reports menu and individual report menu item UI components
(affects report-level rename/delete dropdowns and related menu
navigation).
* **Tests**
* Added tests covering content deletion behavior, multiple-deletion
cases, and data integrity after removals.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Mark provenance of SQL via the branded types SafeSqlFragment and
UntrustedSqlFragment. Only SafeSqlFragment should be executed;
UntrustedSqlFragments require some kind of implicit user approval (show
on screen + user has to click something) before they are promoted to
SafeSqlFragment.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Editor and RLS tester show loading states for inferred/generated SQL
and include a dedicated user SQL editor for safer edits.
* **Refactor**
* Platform-wide SQL handling tightened: snippets and AI-generated SQL
are treated as untrusted/display-only until promoted, improving safety
and consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* Add a generateDeterministicUuid function and tests for it.
* Use the new function and generate an id automatically when creating a snippet.
* Clean up extra code.
* Don't pass in id when creating a snippet.
* Add generateSnippetTitle function and use it instead of fixed string.
* When SQL editor is open, generate an id form a generated snippet title.
* Add id override for SQL editor to avoid flash when saving the snippet.
* Merge the two generate functions to happen in the same useMemo block.
* Save the snippet to the API when adding it.
* Minor fixes from CodeRabbit review.
* Hide new folder CTA in sql editor for self-hosted
* Don't add the snippet for saving, just set the value.
* UpsertContentPayload always has an id.
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
* Hide favorite and share actions for self-hosted version.
* Rename the query on save only on platform.
* Simplify useCheckOpenAiKeyQuery.
* Rename with AI now depends if the OPENAI_API_KEY is set.
* Minor fixes.
* Fix the tests to use .skip for skipping tests. Remove extra port params.
* Make the test for favourites work only on platform variant.
* Bump the deps, refactor deprecated code.
* Migrate keepPreviousData usage.
* Migrate all uses of InfiniteQuery.
* Fix refetchInterval in queries.
* Migrate all use of isLoading to isPending in mutations.
* Fix accessing location in claim-project.
* Fix a bug in duplicate query keys.
* Migrate all queries to use isPending.
* Revert "Fix accessing location in claim-project."
This reverts commit 2a07df64b5.
* Revert the rss.xml file to master.
* Add custom types for queries, mutations and infinite queries.
* Migrate all queries to use the new type.
* Migrate all infinite queries to useCustomInfiniteQueryOptions.
* Migrate all mutations to use useCustomMutationOptions.
* Add type to all imports in `types` folder.
* Migrate all uses of invalidateQueries to use object syntax.
* Migrate the remainder of useInfiniteQuery.
* Migrate all setQueriesData.
* Migrate all fetchQuery uses.
* Migrate some leftover functions from RQ.
* Fix issues found by Charis.
* chore: user v2 content count query for search
Instead of using two different endpoint versions, we can rely on v2 for all cases.
* Update SearchList.tsx
* Refactor search list in sql editor to have context menu
* Add visibility to snippet item in search list tree view
* Address feedback
* Attempt to fix unit test for edit secret modal
* REvert changes to edit secret modal test
* Update SQL editor search to return a flat list
* Open folder when clicking on a snippet from searching and snippet belongs to a folder
* Update snipet page limit
* Set keep previous data true for count
* Add hover card for SQL search list
* Fix issues from feedback
* Make count loader less jarring
* midway
* Render, save, and save configuration of SQL blocks
* Fix re ordering of blocks not showing save UI
* Remove console log
* Smol fix
* Add queryHeight param to queryBlock
* Reinstate telemetry for run query clicked from assistant
* Add
* Add resizing
* Change compactType
* Fix switching reports doesnt update
* ONly show date picker if theres a chart thats using daily stats or infra monitoring
* Fix client crash
* Smol update
* Temp disable count query
* Add additional check for number of snippets.
* Another try at a fix.
---------
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
* chore: paginate user content
* progress
* loading states
* add load more buttons to private snippets
* working pagination
* fix some types
* always show snippet counts
* support new api parameters
* favorite snippets
* progress
* searching
* paginate root folder
* fix renaming snippets
* fix ts
* removed unused prop
* Shift sharing/unsharing query logic outside of valtio to leverage on RQ only
* Fix invalidation on an unsaved snippet
* Clean up
* Fix
* Clean up
* Update API type
* Update API
* fix duplicate snippets error after moving a snippet
* add currently selected snippet
* Fix unsharing a snippet that has yet to be opened
* i'm dumb
* fix sharing a snippet
* fix sharing and unsharing
* show favorite or shared snippet in list even if it's in another page
* Fix wrong import for debounce
* Fix false positive toast error when creating custom report
* Update API type
* Change create new snippet CTA to link back to /new with skip flag
* Fix saving logs explorer query
* Bump page number
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
* ui redesign
* when on a saved query and clicking save, it updates existing query
* fix styles in create dropdown, sticky search
* fix update type err
* fix payload type in logs content update
* fix bug with navigation when clicking dropdown items
* use same dialog ui patterns
* cleanup unused stuff
* rm unused component
* truncate sidebar element labels
* align end in dropdowns, fix zindex issue w expandable sidebar
* add empty state to queries section
* top left of the screen
* Update the design of the sonner toasts. Add the close button by default.
* Migrate studio and www apps to use the SonnerToaster.
* Migrate all toasts from studio.
* Migrate all leftover toasts in studio.
* Add a new toast component with progress. Use it in studio.
* Migrate the design-system app.
* Refactor the consent toast to use sonner.
* Switch docs to use the new sonner toasts.
* Remove toast examples from the design-system app.
* Remove all toast-related components and old code.
* Fix the progress bar in the toast progress component. Also make the bottom components vertically centered.
* Fix the width of the toast progress.
* Use text-foreground-lighter instead of muted for ToastProgress text
* Rename ToastProgress to SonnerProgress.
* Shorten the text in sonner progress.
* Use the correct classes for the close button. Add a const var for the default toast duration. Remove the custom width class from sonner.
* Set the position for all progress toasts to bottom right. Set the duration for all toasts to the default (when reusing a toast id from loading/progress toast, the duration is set to infinity).
* Fix the playwright tests.
* Refactor imports to use ui instead of @ui.
* Change all imports of react-hot-toast with sonner. These components were merged since the last commit to this branch.
* Remove react-hot-toast lib.
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
Co-authored-by: Jonathan Summers-Muir <MildTomato@users.noreply.github.com>
* Show correct number of private snippets for SQL folders
* Ensure that private snippet count is updated when sharing / unsharing queries
* Fix snippets caching issuie
* Fix move query modal showing more than just the project's folders
* Init changes for sql fodlers
* Added upsert logic in sql-editor-v2 valtio, hooked up with templates and quickstarts
* Do up logic for creating new snippets by typing in /new or by clicking new query button
* Do up logic for updating and deleting snippets
* Do up logic for favourites and shared snippets
* Do up logic for favourites and shared snippets
* Fix
* Fix saving indicator, add empty states for favorites and shared snippets
* Implement sorting
* Some minor QOL improvements
* Minor fix on empty state for private snippets
* Add delete folder mutation
* Implement create and update folder
* Fix reinstate with AI renaming for new snippets under folder
* Support controlled multi select behaviour in private snippets
* Undo changes to tree-view-multi-select
* Support bulk deletes
* Support moving queries + rendering queries in folders
* Support deleting folders and creating a new folder when moving a query
* Fix bug where renaming query removes content
* Add initial loading state in sql editor nav + handle fallback if cannot retrieve content by id
* Fix some spelling
* Fix TS issue in sql folders mutation keys
* Fix toggling favorite
* Lint
* Revert fallback behaviour in ]id] for now
* Fix favorites and shared snippets not showing
* Fix moving currently opened snippet leads to loading
* Support bulk moving
* Improve multi select logic a little
* Nit lint
* Reinstate AI retitling for untitled snippets when running query
* Remove hardcode in useAFlag
* Support creating new snippet in a folder directly
* Fix sharing snippets that are within a folder
* Fix sharing snippets within a folder
* Fix favorite
* Add loading state when fetching folder contents
* Fix favoriting snippets in folders
* First round of wrapping RQ errors with handleError
* Remove the throw before the handleError usage.
* Make the handling of an API error more versatile. Add logging in Sentry if the error is of unknown type.
* Remove throwing of the handleError function.
* Add return type to the handleError function to be never so that we're sure it always throws.
---------
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
* Scaffold for API reports
* Add support for filtering database reports by replicas
* midway adding read replica support for custom reports
* Refactor reports page to use react query and deprecate project content mobxstore
* Reports add chart labels if metric is read replica specific
* Give an easier way to remove charts from reports
* make reports layout non-blocking
* Update apps/studio/components/interfaces/Reports/Reports.tsx
Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
---------
Co-authored-by: Alaister Young <a@alaisteryoung.com>
Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
* save chart config when the user changes it
* add optimistic update on change
* fix wrong snippet being updated when changing fast or internet slow
* fix ui state resetting between changes 2
* fix chart config not changing state when working on a new query that only exists locally
* chore: increase react-query stale time
* keep staleTime: 0 for table rows
* use staleTime: 0 for all user sql queries
* use staleTime: 0 for all pg-meta queries
* Some fixes
* fix updating tables
* fix bug while editing column names
* Fix deleting column in database/tables column list not revalidating UI
* Fix updating column in database/tables column list throwing ane rror
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
* Switch over existing delete content to version 2 on API for bulk deletes
* Streamline search behaviour for SQL editor
* Fix SQL query tooltips showing up when dorpdown menu is open or modal is open for non active query
* Shift project snippets up above favourite snippets
* Basic bulk delete query support
* Support shift select in SQL editor for queries
* Fix lint
* Midway implementation notifications v2
* Some updates to Tab component
* Midway implementation notifications v2 again
* implement infinite scrolling and loading for notification sv2
* Add button actions
* Implement archive notifications
* Add filter dropdown for notifications
* Update date format
* Small style fix
* Update
* Update API codegen
* Add empt state
* Add empty state for archived notifications
* Fix type
* Final changes to notifications v2
* Small style fix
* Small fix
* Only mark notifications that come into view as read
* Update package lock
* Only show RR UI if project is enabled
* Only show RR UI if project has read replicas enabled, shift DatabaseSelector state to valtio, add Database Selector to API URl
* Update usage of Project interface from types to Project from project-details-query
* Fix ts errors
* Another one
* Another one...
* God one more
* Fingers crossed
* jesus
* Add deprecate mark on ProjectBase
* Resolve conflicts
* Rename ProjectState from valtio to DatabaseSelectorState
* Rename file
* fix imports
* tiny code changes
* Fixes
---------
Co-authored-by: Alaister Young <a@alaisteryoung.com>