feat(studio): migrate Auth and Realtime reports to OTEL (#50663)

## Problem

PR #50638 migrates API Gateway and Data API reports to OTEL, but the
shared Auth and Realtime metrics still select legacy BigQuery SQL and
the `logs.all` endpoint.

## Fix

Route all active hosted shared API reports through the existing OTEL
builders after feature flags load. Remove unused report variants and
their source-selection abstraction while preserving the legacy BigQuery
path for self-hosted Studio.

This PR is stacked on #50638.

## How to test

- Open the Auth observability report and confirm its seven shared metric
requests use `logs.all.otel` with a `/auth` request-path filter.
- Open the Realtime observability report and confirm its seven shared
metric requests use `logs.all.otel` with a `/realtime` request-path
filter.
- Open the Data API report and confirm its existing OTEL behavior
remains unchanged with a `/rest` request-path filter.
- Expected result: hosted reports wait for ConfigCat before querying,
while self-hosted Studio continues using the legacy BigQuery path.
- Run `./apps/studio/node_modules/.bin/vitest --run
apps/studio/components/interfaces/Reports/Reports.constants.otel.test.ts
--config apps/studio/vitest.config.ts`.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Changes**
- Shared API reports now support filtering by Auth, Realtime, and
PostgREST traffic.
- Filters for Storage, GraphQL, Functions, and other previously
supported traffic types are no longer available.
- Report queries now consistently use edge log data, improving
consistency across request totals, routes, errors, response times, and
network traffic metrics.
- OpenTelemetry-backed reporting is now enabled consistently across
supported report types where available.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Jordi Enric authored and GitHub committed 2026-09-23 09:44:45 +02:00
1 parent 3ec2dfca44
commit d1d9620cdf
1 file changed
+20 -49
@@ -13,26 +13,16 @@ import { safeSql, type SafeLogSqlFragment } from '@/data/logs/safe-analytics-sql
import { reportKeys } from '@/data/reports/keys'
import { IS_PLATFORM } from '@/lib/constants'
const SOURCE_TABLE: Record<string, SafeLogSqlFragment> = {
edge_logs: safeSql`edge_logs`,
function_edge_logs: safeSql`function_edge_logs`,
}
/** Returns a branded source table fragment, falling back to `edge_logs`. */
function sourceTable(src: string): SafeLogSqlFragment {
return SOURCE_TABLE[src] ?? SOURCE_TABLE.edge_logs
}
export const SHARED_API_REPORT_SQL = {
totalRequests: {
queryType: 'logs',
safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment =>
safeSql: (filters: ReportFilterItem[]): SafeLogSqlFragment =>
safeSql`
--reports-api-total-requests
select
cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp,
count(t.id) as count
FROM ${sourceTable(src)} t
FROM edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
@@ -45,7 +35,7 @@ export const SHARED_API_REPORT_SQL = {
},
topRoutes: {
queryType: 'logs',
safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment =>
safeSql: (filters: ReportFilterItem[]): SafeLogSqlFragment =>
safeSql`
-- reports-api-top-routes
select
@@ -54,7 +44,7 @@ export const SHARED_API_REPORT_SQL = {
request.search as search,
response.status_code as status_code,
count(t.id) as count
from ${sourceTable(src)} t
from edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
@@ -69,13 +59,13 @@ export const SHARED_API_REPORT_SQL = {
},
errorCounts: {
queryType: 'logs',
safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment =>
safeSql: (filters: ReportFilterItem[]): SafeLogSqlFragment =>
safeSql`
-- reports-api-error-counts
select
cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp,
count(t.id) as count
FROM ${sourceTable(src)} t
FROM edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
@@ -91,7 +81,7 @@ export const SHARED_API_REPORT_SQL = {
},
topErrorRoutes: {
queryType: 'logs',
safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment =>
safeSql: (filters: ReportFilterItem[]): SafeLogSqlFragment =>
safeSql`
-- reports-api-top-error-routes
select
@@ -100,7 +90,7 @@ export const SHARED_API_REPORT_SQL = {
request.search as search,
response.status_code as status_code,
count(t.id) as count
from ${sourceTable(src)} t
from edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
@@ -117,14 +107,14 @@ export const SHARED_API_REPORT_SQL = {
},
responseSpeed: {
queryType: 'logs',
safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment =>
safeSql: (filters: ReportFilterItem[]): SafeLogSqlFragment =>
safeSql`
-- reports-api-response-speed
select
cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp,
avg(response.origin_time) as avg
FROM
${sourceTable(src)} t
edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
@@ -138,7 +128,7 @@ export const SHARED_API_REPORT_SQL = {
},
topSlowRoutes: {
queryType: 'logs',
safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment =>
safeSql: (filters: ReportFilterItem[]): SafeLogSqlFragment =>
safeSql`
-- reports-api-top-slow-routes
select
@@ -148,7 +138,7 @@ export const SHARED_API_REPORT_SQL = {
response.status_code as status_code,
count(t.id) as count,
avg(response.origin_time) as avg
from ${sourceTable(src)} t
from edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
@@ -163,7 +153,7 @@ export const SHARED_API_REPORT_SQL = {
},
networkTraffic: {
queryType: 'logs',
safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment =>
safeSql: (filters: ReportFilterItem[]): SafeLogSqlFragment =>
safeSql`
-- reports-api-network-traffic
select
@@ -187,7 +177,7 @@ export const SHARED_API_REPORT_SQL = {
0
) as egress_mb,
FROM
${sourceTable(src)} t
edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
@@ -204,13 +194,7 @@ export const SHARED_API_REPORT_SQL = {
export type SharedAPIReportKey = keyof typeof SHARED_API_REPORT_SQL
export type SharedAPIReportFilterBy =
| 'auth'
| 'realtime'
| 'storage'
| 'graphql'
| 'functions'
| 'postgrest'
export type SharedAPIReportFilterBy = 'auth' | 'realtime' | 'postgrest'
type SharedAPIReportParams = {
filterBy: SharedAPIReportFilterBy
@@ -230,20 +214,8 @@ export const useSharedAPIReport = ({
const queryClient = useQueryClient()
const flagUseOtel = useFlag('otelReports')
const { hasLoaded: hasLoadedFlags } = useContext(FeatureFlagContext)
const filterByMapSource = {
functions: 'function_edge_logs',
realtime: 'edge_logs',
storage: 'edge_logs',
graphql: 'edge_logs',
postgrest: 'edge_logs',
auth: 'edge_logs',
}
const filterByMapValue = {
functions: '/functions',
const filterByMapValue: Record<SharedAPIReportFilterBy, string> = {
realtime: '/realtime',
storage: '/storage',
graphql: '/graphql',
postgrest: '/rest',
auth: '/auth',
}
@@ -255,15 +227,14 @@ export const useSharedAPIReport = ({
}
const allFilters = [baseFilter, ...filters]
const source = filterByMapSource[filterBy]
const isDataApiReport = filterBy === 'postgrest'
const useOtel = IS_PLATFORM && isDataApiReport && Boolean(flagUseOtel)
const isQueryModeReady = !IS_PLATFORM || !isDataApiReport || hasLoadedFlags === true
const source = 'edge_logs'
const useOtel = IS_PLATFORM && Boolean(flagUseOtel)
const isQueryModeReady = !IS_PLATFORM || hasLoadedFlags === true
const keys = Object.keys(SHARED_API_REPORT_SQL) as SharedAPIReportKey[]
const getQuerySql = (queryName: SharedAPIReportKey) =>
useOtel
? getLogsSql(PRESET_CONFIG.api.queries[queryName], allFilters, true)
: SHARED_API_REPORT_SQL[queryName].safeSql(allFilters, source)
: SHARED_API_REPORT_SQL[queryName].safeSql(allFilters)
const SQLMap: Record<SharedAPIReportKey, SafeLogSqlFragment> = {
totalRequests: getQuerySql('totalRequests'),
topRoutes: getQuerySql('topRoutes'),