diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx index cef116eb6ec..e61aaefc6a9 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx @@ -67,10 +67,7 @@ import { useTableRowSelection } from '@/components/ui/DataTable/useTableRowSelec import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip' import { useUnifiedLogsChartQuery } from '@/data/logs/unified-logs-chart-query' import { useUnifiedLogsCountQuery } from '@/data/logs/unified-logs-count-query' -import { - useUnifiedLogsBackend, - useUnifiedLogsInfiniteQuery, -} from '@/data/logs/unified-logs-infinite-query' +import { useUnifiedLogsInfiniteQuery } from '@/data/logs/unified-logs-infinite-query' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' import { useShowMultigresLogs } from '@/hooks/misc/useShowMultigresLogs' import { useTrack } from '@/lib/telemetry/track' @@ -96,7 +93,6 @@ export const UnifiedLogs = () => { useResetFocus() const { ref: projectRef } = useParams() - const { isReady: isLogsBackendReady } = useUnifiedLogsBackend() const track = useTrack() const [search, setSearch] = useQueryStates(SEARCH_PARAMS_PARSER) const showMultigresLogs = useShowMultigresLogs() @@ -213,7 +209,6 @@ export const UnifiedLogs = () => { }) const refetchAllData = () => { - if (!isLogsBackendReady) return refetchLogs() refetchCounts() refetchCharts() diff --git a/apps/studio/data/logs/get-unified-logs.ts b/apps/studio/data/logs/get-unified-logs.ts index fbcc0c4dc71..f9030b2b966 100644 --- a/apps/studio/data/logs/get-unified-logs.ts +++ b/apps/studio/data/logs/get-unified-logs.ts @@ -1,9 +1,8 @@ import { useMutation } from '@tanstack/react-query' -import { useFlag } from 'common' import { toast } from 'sonner' import { logsAllEndpointUrl, pickLogsQueryBuilder } from './logs-endpoint' -import { getUnifiedLogsISOStartEnd } from './unified-logs-infinite-query' +import { getUnifiedLogsISOStartEnd, useUnifiedLogsBackend } from './unified-logs-infinite-query' import { mapUnifiedLogRow, parseUnifiedLogsQueryRows } from './unified-logs.utils' import { getUnifiedLogsQuery } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.queries' import { getUnifiedLogsQuery as getUnifiedLogsQueryBq } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.queries.bq' @@ -58,7 +57,7 @@ export const useGetUnifiedLogsMutation = ({ UseCustomMutationOptions, 'mutationFn' > = {}) => { - const useOtel = useFlag('otelUnifiedLogs') + const useOtel = useUnifiedLogsBackend() return useMutation({ mutationFn: (vars) => retrieveUnifiedLogs({ ...vars, useOtel: vars.useOtel ?? useOtel }), async onSuccess(data, variables, context) { diff --git a/apps/studio/data/logs/unified-log-inspection-query.ts b/apps/studio/data/logs/unified-log-inspection-query.ts index 216a173fab2..43894fbe7d1 100644 --- a/apps/studio/data/logs/unified-log-inspection-query.ts +++ b/apps/studio/data/logs/unified-log-inspection-query.ts @@ -1,5 +1,4 @@ import { useQuery } from '@tanstack/react-query' -import { useFlag } from 'common' import { executeAnalyticsSql } from './execute-analytics-sql' import { logsKeys } from './keys' @@ -12,6 +11,7 @@ import { analyticsLiteral as lit, safeSql, type SafeLogSqlFragment } from './saf import { getUnifiedLogsISOStartEnd, UNIFIED_LOGS_QUERY_OPTIONS, + useUnifiedLogsBackend, } from './unified-logs-infinite-query' import { getAuthServiceFlowQuery, @@ -301,7 +301,7 @@ export const useUnifiedLogInspectionQuery = ( ...options }: UseCustomQueryOptions = {} ) => { - const useOtel = !!useFlag('otelUnifiedLogs') + const useOtel = useUnifiedLogsBackend() return useQuery({ queryKey: [...logsKeys.serviceFlow(projectRef, search, logId), { otel: useOtel }], queryFn: ({ signal }) => diff --git a/apps/studio/data/logs/unified-logs-chart-query.ts b/apps/studio/data/logs/unified-logs-chart-query.ts index ef9f1289856..2af1561f304 100644 --- a/apps/studio/data/logs/unified-logs-chart-query.ts +++ b/apps/studio/data/logs/unified-logs-chart-query.ts @@ -166,14 +166,11 @@ export const useUnifiedLogsChartQuery = ( ...options }: UseCustomQueryOptions = {} ) => { - const { isReady, useOtel } = useUnifiedLogsBackend() + const useOtel = useUnifiedLogsBackend() return useQuery({ - queryKey: [...logsKeys.unifiedLogsChart(projectRef, search), { otel: useOtel, ready: isReady }], - queryFn: ({ signal }) => { - if (!isReady) throw new Error('Unified Logs backend is not ready') - return getUnifiedLogsChart({ projectRef, search, useOtel }, signal) - }, - enabled: enabled && isReady && typeof projectRef !== 'undefined', + queryKey: [...logsKeys.unifiedLogsChart(projectRef, search), { otel: useOtel }], + queryFn: ({ signal }) => getUnifiedLogsChart({ projectRef, search, useOtel }, signal), + enabled: enabled && typeof projectRef !== 'undefined', placeholderData: keepPreviousData, ...UNIFIED_LOGS_QUERY_OPTIONS, ...options, diff --git a/apps/studio/data/logs/unified-logs-count-query.ts b/apps/studio/data/logs/unified-logs-count-query.ts index 55bb0e9472a..69fae9f7817 100644 --- a/apps/studio/data/logs/unified-logs-count-query.ts +++ b/apps/studio/data/logs/unified-logs-count-query.ts @@ -90,14 +90,11 @@ export const useUnifiedLogsCountQuery = ( ...options }: UseCustomQueryOptions = {} ) => { - const { isReady, useOtel } = useUnifiedLogsBackend() + const useOtel = useUnifiedLogsBackend() return useQuery({ - queryKey: [...logsKeys.unifiedLogsCount(projectRef, search), { otel: useOtel, ready: isReady }], - queryFn: ({ signal }) => { - if (!isReady) throw new Error('Unified Logs backend is not ready') - return getUnifiedLogsCount({ projectRef, search, useOtel }, signal) - }, - enabled: enabled && isReady && typeof projectRef !== 'undefined', + queryKey: [...logsKeys.unifiedLogsCount(projectRef, search), { otel: useOtel }], + queryFn: ({ signal }) => getUnifiedLogsCount({ projectRef, search, useOtel }, signal), + enabled: enabled && typeof projectRef !== 'undefined', ...UNIFIED_LOGS_QUERY_OPTIONS, ...options, }) diff --git a/apps/studio/data/logs/unified-logs-facet-count-query.ts b/apps/studio/data/logs/unified-logs-facet-count-query.ts index ef26390917e..43dde999327 100644 --- a/apps/studio/data/logs/unified-logs-facet-count-query.ts +++ b/apps/studio/data/logs/unified-logs-facet-count-query.ts @@ -1,5 +1,4 @@ import { useQuery } from '@tanstack/react-query' -import { useFlag } from 'common' import { executeAnalyticsSql } from './execute-analytics-sql' import { logsKeys } from './keys' @@ -9,6 +8,7 @@ import { getUnifiedLogsISOStartEnd, UNIFIED_LOGS_QUERY_OPTIONS, UnifiedLogsVariables, + useUnifiedLogsBackend, } from './unified-logs-infinite-query' import { getFacetCountQuery } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.queries' import { @@ -65,7 +65,7 @@ export const useUnifiedLogsFacetCountQuery = ...options }: UseCustomQueryOptions = {} ) => { - const useOtel = !!useFlag('otelUnifiedLogs') + const useOtel = useUnifiedLogsBackend() return useQuery({ queryKey: [ ...logsKeys.unifiedLogsFacetCount(projectRef, facet, facetSearch, search), diff --git a/apps/studio/data/logs/unified-logs-infinite-query.ts b/apps/studio/data/logs/unified-logs-infinite-query.ts index 1cae0c775a0..8360f74d2b9 100644 --- a/apps/studio/data/logs/unified-logs-infinite-query.ts +++ b/apps/studio/data/logs/unified-logs-infinite-query.ts @@ -13,7 +13,6 @@ import { QuerySearchParamsType, } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.types' import { handleError } from '@/data/fetchers' -import { IS_PLATFORM } from '@/lib/constants' import type { ResponseError, UseCustomInfiniteQueryOptions } from '@/types' const LOGS_PAGE_LIMIT = 50 @@ -30,14 +29,7 @@ export type UnifiedLogsData = any export type UnifiedLogsError = ResponseError export type UnifiedLogsVariables = { projectRef?: string; search: QuerySearchParamsType } -export const useUnifiedLogsBackend = () => { - const { hasLoaded, configcat } = useFeatureFlags() - const useOtel = IS_PLATFORM && configcat.otelUnifiedLogs === true - const isReady = - !IS_PLATFORM || (hasLoaded === true && typeof configcat.otelUnifiedLogs === 'boolean') - - return { isReady, useOtel } -} +export const useUnifiedLogsBackend = () => useFeatureFlags().configcat.otelUnifiedLogs !== false export const getUnifiedLogsISOStartEnd = ( search: QuerySearchParamsType, @@ -162,17 +154,12 @@ export const useUnifiedLogsInfiniteQuery = ( PageParam | null > = {} ) => { - const { isReady, useOtel } = useUnifiedLogsBackend() + const useOtel = useUnifiedLogsBackend() return useInfiniteQuery({ - queryKey: [ - ...logsKeys.unifiedLogsInfinite(projectRef, search), - { otel: useOtel, ready: isReady }, - ], - queryFn: ({ signal, pageParam }) => { - if (!isReady) throw new Error('Unified Logs backend is not ready') - return getUnifiedLogs({ projectRef, search, pageParam, useOtel }, signal) - }, - enabled: enabled && isReady && typeof projectRef !== 'undefined', + queryKey: [...logsKeys.unifiedLogsInfinite(projectRef, search), { otel: useOtel }], + queryFn: ({ signal, pageParam }) => + getUnifiedLogs({ projectRef, search, pageParam, useOtel }, signal), + enabled: enabled && typeof projectRef !== 'undefined', placeholderData: keepPreviousData, getPreviousPageParam: (firstPage) => { if (!firstPage.prevCursor) return null diff --git a/apps/studio/data/logs/unified-logs-query.test.tsx b/apps/studio/data/logs/unified-logs-query.test.tsx index 525b600cb64..5b45c0432cc 100644 --- a/apps/studio/data/logs/unified-logs-query.test.tsx +++ b/apps/studio/data/logs/unified-logs-query.test.tsx @@ -9,25 +9,14 @@ import { useUnifiedLogsCountQuery } from './unified-logs-count-query' import { useUnifiedLogsInfiniteQuery } from './unified-logs-infinite-query' import type { QuerySearchParamsType } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.types' -const { mockExecuteAnalyticsSql, mockIsPlatform } = vi.hoisted(() => ({ +const { mockExecuteAnalyticsSql } = vi.hoisted(() => ({ mockExecuteAnalyticsSql: vi.fn(), - mockIsPlatform: { value: true }, })) vi.mock('./execute-analytics-sql', () => ({ executeAnalyticsSql: mockExecuteAnalyticsSql, })) -vi.mock('@/lib/constants', async () => { - const actual = await vi.importActual>('@/lib/constants') - return { - ...actual, - get IS_PLATFORM() { - return mockIsPlatform.value - }, - } -}) - type FlagState = { hasLoaded: boolean otelUnifiedLogs?: boolean @@ -60,28 +49,29 @@ const createWrapper = (queryClient: QueryClient) => { } } +const search: QuerySearchParamsType = { + filter: null, + latency: null, + 'timing.dns': null, + 'timing.connection': null, + 'timing.tls': null, + 'timing.ttfb': null, + 'timing.transfer': null, + date: null, + sort: null, + size: 40, + start: 0, + direction: 'next', + cursor: new Date(), + id: null, + show_connection_logs: true, + edge_auth: true, + edge_storage: true, + edge_postgrest: true, + user: null, +} + const useUnifiedLogsQueries = () => { - const search: QuerySearchParamsType = { - filter: null, - latency: null, - 'timing.dns': null, - 'timing.connection': null, - 'timing.tls': null, - 'timing.ttfb': null, - 'timing.transfer': null, - date: null, - sort: null, - size: 40, - start: 0, - direction: 'next', - cursor: new Date(), - id: null, - show_connection_logs: true, - edge_auth: true, - edge_storage: true, - edge_postgrest: true, - user: null, - } const variables = { projectRef: 'project-ref', search } return { chart: useUnifiedLogsChartQuery(variables), @@ -96,7 +86,6 @@ describe('unified logs queries', () => { let queryClient: QueryClient beforeEach(() => { - mockIsPlatform.value = true flagState.hasLoaded = false flagState.otelUnifiedLogs = undefined flagState.unrelatedFlag = undefined @@ -109,12 +98,10 @@ describe('unified logs queries', () => { vi.clearAllMocks() }) - test('does not request analytics while flags are pending', async () => { + test('uses OTEL for initial and manual requests while flags are pending', async () => { const { result } = renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) }) - await Promise.resolve() - - expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled() + await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3)) await Promise.all([ result.current.chart.refetch(), @@ -122,23 +109,28 @@ describe('unified logs queries', () => { result.current.logs.refetch(), ]) - expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled() + expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(6) + expect(endpoints()).toEqual( + Array(6).fill('/platform/projects/{ref}/analytics/endpoints/logs.all.otel') + ) }) - test('starts only ClickHouse requests when the flag resolves enabled', async () => { + test('does not start or abort a legacy batch before the flag resolves enabled', async () => { const { rerender } = renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) }) + await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3)) + expect(endpoints()).toEqual( + Array(3).fill('/platform/projects/{ref}/analytics/endpoints/logs.all.otel') + ) + flagState.hasLoaded = true flagState.otelUnifiedLogs = true rerender() - await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3)) + await Promise.resolve() - expect(endpoints()).toEqual([ - '/platform/projects/{ref}/analytics/endpoints/logs.all.otel', - '/platform/projects/{ref}/analytics/endpoints/logs.all.otel', - '/platform/projects/{ref}/analytics/endpoints/logs.all.otel', - ]) + expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3) + expect(mockExecuteAnalyticsSql.mock.calls.every(([args]) => !args.signal.aborted)).toBe(true) }) test('allows BigQuery requests when the flag resolves disabled', async () => { @@ -156,52 +148,26 @@ describe('unified logs queries', () => { ]) }) - test('does not select BigQuery when ConfigCat fails', async () => { + test('defaults to OTEL when ConfigCat fails', async () => { flagState.hasLoaded = true renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) }) - await Promise.resolve() - - expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled() + await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3)) + expect(endpoints()).toEqual( + Array(3).fill('/platform/projects/{ref}/analytics/endpoints/logs.all.otel') + ) }) - test('does not select BigQuery when the loaded flags omit the backend flag', async () => { + test('defaults to OTEL when the loaded flags omit the backend flag', async () => { flagState.hasLoaded = true flagState.unrelatedFlag = true renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) }) - await Promise.resolve() - - expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled() - }) - - test('keeps BigQuery available when self-hosted flags are disabled', async () => { - mockIsPlatform.value = false - - renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) }) - await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3)) - - expect(endpoints()).toEqual([ - '/platform/projects/{ref}/analytics/endpoints/logs.all', - '/platform/projects/{ref}/analytics/endpoints/logs.all', - '/platform/projects/{ref}/analytics/endpoints/logs.all', - ]) - }) - - test('does not start and abort a legacy batch before enabled flags resolve', async () => { - const { rerender } = renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) }) - - await Promise.resolve() - expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled() - - flagState.hasLoaded = true - flagState.otelUnifiedLogs = true - rerender() - - await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3)) - expect(endpoints()).not.toContain('/platform/projects/{ref}/analytics/endpoints/logs.all') + expect(endpoints()).toEqual( + Array(3).fill('/platform/projects/{ref}/analytics/endpoints/logs.all.otel') + ) }) })