mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## Problem Moving the Logs Explorer to ClickHouse means users' saved BigQuery queries no longer run. <img width="2430" height="1010" alt="CleanShot 2026-06-29 at 11 36 04@2x" src="https://github.com/user-attachments/assets/ae0ab155-7d3d-4ae9-81c3-22bf3a88cf8c" /> ## Fix Rewrite the query with AI instead of a SQL transpiler. AI handles the long tail of nested fields and dialect differences far better than a rule-based rewriter, and it needs no extra runtime dependency. - `rewriteLogsSqlWithAI` posts the current query to `/api/ai/code/complete` with `dialect: 'clickhouse'`. The endpoint skips the Postgres schema and best-practices for that dialect and uses logs-specific instructions and model so the output is ClickHouse logs SQL (FROM `logs` + `source` filter, no `unnest` joins, nested fields read from `log_attributes['...']`). - The query's `source` is detected and its real `log_attributes` keys are fetched and passed to the model, so it maps to exact paths instead of guessing. - The rewrite runs in the background and is proposed as a side-by-side accept/discard diff in the editor. The AI Assistant panel is not opened. - Entry points: a banner shown only for legacy-looking queries (dismissal persisted), and a "Fix Query" button next to Field Reference. - The Field Reference drawers discover `log_attributes` keys from real data so the listed fields match what the source actually emits. ## Dependencies Built on top of #47265 (Logs Explorer -> OTEL endpoint) — that is the base branch of this PR. Merge #47265 first. Behind `otelLegacyLogs` (off by default). Part of DEBUG-145 (split from #47087). ## How to test - Open the Logs Explorer with a BigQuery logs query (the templates have some), click "Fix Query", and confirm the diff shows valid ClickHouse SQL. Accept it and confirm the applied query runs. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an OTEL legacy logs workflow (behind a feature flag) with an interactive banner and a “Fix Query” ClickHouse rewrite action, including an accept/discard diff review overlay. * Introduced OTEL-aware field reference rendering with dynamic discovery of `log_attributes` keys and updated OTEL source insertion behavior. * Enabled dialect-aware SQL completion for ClickHouse logs, using logs-specific instructions and output constraints. * **Bug Fixes** * Improved rewrite flow validation and handling, including log source detection and cleanup of AI-generated SQL formatting. * **Tests** * Added Vitest coverage for rewrite prompt generation, detection/classification utilities, SQL fence stripping, OTEL field mapping, and OTEL log attribute key discovery. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
138 lines
3.3 KiB
TypeScript
138 lines
3.3 KiB
TypeScript
import { createParser, useQueryState } from 'nuqs'
|
|
import { useCallback } from 'react'
|
|
|
|
import { Filters } from '@/components/interfaces/Settings/Logs/Logs.types'
|
|
|
|
interface LogsUrlState {
|
|
search: string
|
|
timestampStart: string
|
|
timestampEnd: string
|
|
selectedLogId: string | null
|
|
filters: Filters
|
|
}
|
|
|
|
const defaultState: LogsUrlState = {
|
|
search: '',
|
|
timestampStart: '',
|
|
timestampEnd: '',
|
|
selectedLogId: null,
|
|
filters: {},
|
|
}
|
|
|
|
const stringWithDefault = (defaultValue: string) =>
|
|
createParser({
|
|
parse: (v) => v ?? defaultValue,
|
|
serialize: (v) => v || '',
|
|
})
|
|
|
|
const jsonWithDefault = <T>(defaultValue: T) =>
|
|
createParser({
|
|
parse: (v) => {
|
|
if (!v) return defaultValue
|
|
try {
|
|
return JSON.parse(v) as T
|
|
} catch (e) {
|
|
return defaultValue
|
|
}
|
|
},
|
|
serialize: (v) => {
|
|
if (!v) return ''
|
|
try {
|
|
return JSON.stringify(v)
|
|
} catch (e) {
|
|
return ''
|
|
}
|
|
},
|
|
})
|
|
|
|
export function useLogsUrlState() {
|
|
const [searchValue, setSearchValue] = useQueryState('s', stringWithDefault(defaultState.search))
|
|
const [timestampStartValue, setTimestampStartValue] = useQueryState(
|
|
'its',
|
|
stringWithDefault(defaultState.timestampStart)
|
|
)
|
|
const [timestampEndValue, setTimestampEndValue] = useQueryState(
|
|
'ite',
|
|
stringWithDefault(defaultState.timestampEnd)
|
|
)
|
|
const [selectedLogIdValue, setSelectedLogIdValue] = useQueryState('log', stringWithDefault(''))
|
|
const [filtersValue, setFiltersValue] = useQueryState(
|
|
'f',
|
|
jsonWithDefault<Filters>(defaultState.filters)
|
|
)
|
|
|
|
// Ensure we never return null values
|
|
const search = searchValue || defaultState.search
|
|
const timestampStart = timestampStartValue || defaultState.timestampStart
|
|
const timestampEnd = timestampEndValue || defaultState.timestampEnd
|
|
const selectedLogId = selectedLogIdValue || null
|
|
const filters = filtersValue || defaultState.filters
|
|
|
|
const setSearch = useCallback(
|
|
(value: string) => {
|
|
setSearchValue(value || defaultState.search)
|
|
},
|
|
[setSearchValue]
|
|
)
|
|
|
|
const setTimeRange = useCallback(
|
|
(start: string, end: string) => {
|
|
setTimestampStartValue(start || defaultState.timestampStart)
|
|
setTimestampEndValue(end || defaultState.timestampEnd)
|
|
},
|
|
[setTimestampStartValue, setTimestampEndValue]
|
|
)
|
|
|
|
const setSelectedLogId = useCallback(
|
|
(value: string | null) => {
|
|
setSelectedLogIdValue(value || '')
|
|
},
|
|
[setSelectedLogIdValue]
|
|
)
|
|
|
|
const setFilters = useCallback(
|
|
(value: Filters) => {
|
|
setFiltersValue(value || defaultState.filters)
|
|
},
|
|
[setFiltersValue]
|
|
)
|
|
|
|
const updateFilter = useCallback(
|
|
(key: string, value: any) => {
|
|
setFilters({ ...filters, [key]: value })
|
|
},
|
|
[filters, setFilters]
|
|
)
|
|
|
|
const reset = useCallback(() => {
|
|
setSearch(defaultState.search)
|
|
setTimestampStartValue(defaultState.timestampStart)
|
|
setTimestampEndValue(defaultState.timestampEnd)
|
|
setSelectedLogIdValue('')
|
|
setFiltersValue(defaultState.filters)
|
|
}, [
|
|
setSearch,
|
|
setTimestampStartValue,
|
|
setTimestampEndValue,
|
|
setSelectedLogIdValue,
|
|
setFiltersValue,
|
|
])
|
|
|
|
return {
|
|
// State
|
|
search,
|
|
timestampStart,
|
|
timestampEnd,
|
|
selectedLogId,
|
|
filters,
|
|
|
|
// Setters
|
|
setSearch,
|
|
setTimeRange,
|
|
setSelectedLogId,
|
|
setFilters,
|
|
updateFilter,
|
|
reset,
|
|
}
|
|
}
|