Third of a stack. **Stacked on #49070** (which is stacked on #49069) — review those first. Base retargets automatically as each merges. Mechanical throughout; no behavior change. ## The problem Three types described where a query runs, and no two agreed: | | shape | |---|---| | `CellSource` (registry) | `{ id, type, parameters: { … } }` — `id` and `type` always held the same literal | | `QuerySource` (SQL editor) | `{ type: 'database' } \| { type: 'logs', dateRange }` | | notebook cells | flat per-backend fields, neither of the above | Anything crossing between them needed a translation that dropped fields on the way — which is how a notebook cell's replica selection had nowhere to go. ## What changed One `QuerySourceBinding`: a backend `_tag` with that backend's parameters spread flat beside it, borrowed from the wire schema (#49069) so the binding and the persisted cell agree by construction. - **`QuerySource` is deleted.** `useRunSource` returns the shared binding, so `runSource.type`/`dateRange` become `_tag`/`time_range` across the SQL editor — that is most of the file count here. - **`getQuerySourceBinding`** projects a notebook cell onto a binding; **`toQuerySourceBinding`** does the same for any backend-tagged carrier. Both overloaded so an already-narrowed caller gets the matching binding back rather than the union, which keeps the result spreadable without re-narrowing. - **`ExplorerQuerySourceMenu`** drops its inline copy of the custom-range and upgrade-prompt logic in favor of `useLogsCustomRange`, which the SQL editor menu already used. The registry keeps only what is genuinely runtime: endpoints, labels, icons, availability, defaults. What a query *is* stays in the wire schema. ## Verification Typecheck, Prettier, and the lint ratchet clean. 405 tests pass across the notebook schema, query sources, the logs components, the SQL editor, and the Explorer surfaces. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * Updated query source handling across Explorer and SQL Editor for a more consistent selection experience. * Database and log sources now preserve identifiers and time ranges more reliably when switching or editing queries. * Source menus, labels, icons, validation, and query execution now reflect the selected source more accurately. * **Bug Fixes** * Invalid or outdated saved source settings now safely fall back to a database source. * Improved log-source detection and time-range handling throughout query editing and execution. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
UI Testing Notes
Rules
-
All tests should be run consistently (avoid situations whereby tests fails "sometimes")
-
Group tests in folders based on the feature they are testing. Avoid file/folder based folder names since those can change and we will forget to update the tests.
Examples: /logs /reports /projects /database-settings /auth
Custom Render and Custom Render Hook
customRender and customRenderHook are wrappers around render and renderHook that add some necessary providers like QueryClientProvider, TooltipProvider and NuqsTestingAdapter.
Generally use those instead of the default render and renderHook functions.
import { customRender, customRenderHook } from 'tests/lib/custom-render'
customRender(<MyComponent />)
customRenderHook(() => useMyHook())
Mocking API Requests
To mock API requests, we use the msw library.
Global mocks can be found in tests/lib/msw-global-api-mocks.ts.
To mock an endpoint you can use the addAPIMock function. Make sure to add the mock in the beforeEach hook. It won't work with beforeAll if you have many tests.
beforeEach(() => {
addAPIMock({
method: 'get',
path: '/api/my-endpoint',
response: {
data: { foo: 'bar' },
},
})
})
API Mocking Tips:
- Keep mocks in the same folder as the tests that use them
- Add a test to verify the mock is working
This will make debugging and updating the mocks easier.
test('mock is working', async () => {
const response = await fetch('/api/my-endpoint')
expect(response.json()).resolves.toEqual({ data: { foo: 'bar' } })
})
Mocking Nuqs URL Parameters
To render a component that uses Nuqs with some predefined query parameters, you can use customRender with the nuqs prop.
customRender(<MyComponent />, {
nuqs: {
searchParams: {
search: 'hello world',
},
},
})
<Popover> vs <Dropdown>
When simulating clicks on these components, do the following:
// for Popovers
import userEvent from '@testing-library/user-event'
await userEvent.click('Hello world')
// for Dropdowns
import clickDropdown from 'tests/helpers'
clickDropdown('Hello world')