mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 11:25:06 +03:00
## Summary Part 4 of the SafeSql migration stack ([#45897](https://github.com/supabase/supabase/pull/45897), [#45903](https://github.com/supabase/supabase/pull/45903), [#45990](https://github.com/supabase/supabase/pull/45990), this PR, …). Converts the remaining reports, query performance, observability, index advisor, and privileges call sites of `executeSql` to produce `SafeSqlFragment` values. The `ReportQuery.sql` field flips from `string` to `SafeSqlFragment`, which cascades into every consumer — landed here atomically so each branch typechecks cleanly. Touched areas: - `interfaces/Reports/*` — `ReportQuery.sql: SafeSqlFragment`, plus all report definitions/utilities updated - `interfaces/QueryPerformance/useQueryPerformanceQuery.ts` - `interfaces/Database/IndexAdvisor/*` and `data/database/{table-index-advisor,retrieve-index-advisor-result}-query.ts` - `data/privileges/{table-api-access,update-exposed-entities}-mutation.ts` - `interfaces/Storage/StoragePolicies/StoragePolicies.tsx` - `hooks/analytics/useDbQuery.tsx` - `Observability/useSlowQueriesCount.ts` + `useQueryInsightsIssues.utils.test.ts` ## Test plan - [x] `pnpm typecheck` passes - [x] `useQueryInsightsIssues.utils.test.ts` passes - [x] Dev-server smoke test: reports pages, query performance, index advisor, storage policies <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Reworked SQL construction and typings across reporting, query performance, index advisor, and privilege features to use safer SQL fragments, improving reliability and preventing query composition issues. * **Types** * Reporting query types were split to distinguish database vs. logs queries, enabling correct handling and validation. * **Docs/Utils** * Added a helper to consistently generate logs SQL for report hooks. * **Tests** * Updated tests to exercise the new SQL-building API. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45998) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
168 lines
5.4 KiB
TypeScript
168 lines
5.4 KiB
TypeScript
import { useParams } from 'common'
|
|
import { isEqual } from 'lodash'
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import { PRESET_CONFIG } from '@/components/interfaces/Reports/Reports.constants'
|
|
import { ReportFilterItem } from '@/components/interfaces/Reports/Reports.types'
|
|
import { getLogsSql, queriesFactory } from '@/components/interfaces/Reports/Reports.utils'
|
|
import type { LogsEndpointParams } from '@/components/interfaces/Settings/Logs/Logs.types'
|
|
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
|
|
|
|
export const useApiReport = () => {
|
|
const { ref: projectRef } = useParams()
|
|
const state = useDatabaseSelectorStateSnapshot()
|
|
|
|
const identifier = state.selectedDatabaseId
|
|
const [filters, setFilters] = useState<ReportFilterItem[]>([])
|
|
|
|
const queryHooks = queriesFactory<keyof typeof PRESET_CONFIG.api.queries>(
|
|
PRESET_CONFIG.api.queries,
|
|
projectRef ?? 'default'
|
|
)
|
|
const totalRequests = queryHooks.totalRequests()
|
|
const topRoutes = queryHooks.topRoutes()
|
|
const errorCounts = queryHooks.errorCounts()
|
|
const topErrorRoutes = queryHooks.topErrorRoutes()
|
|
const responseSpeed = queryHooks.responseSpeed()
|
|
const topSlowRoutes = queryHooks.topSlowRoutes()
|
|
const networkTraffic = queryHooks.networkTraffic()
|
|
const requestsByCountry = queryHooks.requestsByCountry()
|
|
const activeHooks = [
|
|
totalRequests,
|
|
topRoutes,
|
|
errorCounts,
|
|
topErrorRoutes,
|
|
responseSpeed,
|
|
topSlowRoutes,
|
|
networkTraffic,
|
|
requestsByCountry,
|
|
]
|
|
|
|
const addFilter = (filter: ReportFilterItem) => {
|
|
// use a deep equal when comparing objects.
|
|
if (filters.some((f) => isEqual(f, filter))) return
|
|
setFilters((prev) =>
|
|
[...prev, filter].sort((a, b) => {
|
|
const keyA = a.key.toLowerCase()
|
|
const keyB = b.key.toLowerCase()
|
|
if (keyA < keyB) {
|
|
return -1
|
|
}
|
|
if (keyA > keyB) {
|
|
return 1
|
|
}
|
|
return 0
|
|
})
|
|
)
|
|
}
|
|
const removeFilter = (filter: ReportFilterItem) => removeFilters([filter])
|
|
const removeFilters = (toRemove: ReportFilterItem[]) => {
|
|
setFilters((prev) => {
|
|
return prev.filter((f) => !toRemove.find((r) => isEqual(f, r)))
|
|
})
|
|
}
|
|
|
|
// [Joshen] Keeping database selector separate from filter state, and merging them here for simplicity
|
|
const formattedFilters: ReportFilterItem[] = [
|
|
...filters,
|
|
...(identifier !== undefined
|
|
? [{ key: 'identifier', value: `'${identifier}'`, compare: 'is' } as ReportFilterItem]
|
|
: []),
|
|
]
|
|
|
|
useEffect(() => {
|
|
// update sql for each query
|
|
if (totalRequests.changeQuery) {
|
|
totalRequests.changeQuery(
|
|
getLogsSql(PRESET_CONFIG.api.queries.totalRequests, formattedFilters)
|
|
)
|
|
}
|
|
if (topRoutes.changeQuery) {
|
|
topRoutes.changeQuery(getLogsSql(PRESET_CONFIG.api.queries.topRoutes, formattedFilters))
|
|
}
|
|
if (errorCounts.changeQuery) {
|
|
errorCounts.changeQuery(getLogsSql(PRESET_CONFIG.api.queries.errorCounts, formattedFilters))
|
|
}
|
|
|
|
if (topErrorRoutes.changeQuery) {
|
|
topErrorRoutes.changeQuery(
|
|
getLogsSql(PRESET_CONFIG.api.queries.topErrorRoutes, formattedFilters)
|
|
)
|
|
}
|
|
if (responseSpeed.changeQuery) {
|
|
responseSpeed.changeQuery(
|
|
getLogsSql(PRESET_CONFIG.api.queries.responseSpeed, formattedFilters)
|
|
)
|
|
}
|
|
|
|
if (topSlowRoutes.changeQuery) {
|
|
topSlowRoutes.changeQuery(
|
|
getLogsSql(PRESET_CONFIG.api.queries.topSlowRoutes, formattedFilters)
|
|
)
|
|
}
|
|
|
|
if (networkTraffic.changeQuery) {
|
|
networkTraffic.changeQuery(
|
|
getLogsSql(PRESET_CONFIG.api.queries.networkTraffic, formattedFilters)
|
|
)
|
|
}
|
|
if (requestsByCountry.changeQuery) {
|
|
requestsByCountry.changeQuery(
|
|
getLogsSql(PRESET_CONFIG.api.queries.requestsByCountry, formattedFilters)
|
|
)
|
|
}
|
|
}, [JSON.stringify(formattedFilters)])
|
|
|
|
const handleRefresh = async () => {
|
|
activeHooks.forEach((hook) => hook.runQuery())
|
|
}
|
|
|
|
const handleSetParams = (params: Partial<LogsEndpointParams>) => {
|
|
activeHooks.forEach((hook) => {
|
|
hook.setParams?.((prev: LogsEndpointParams) => ({ ...prev, ...params }))
|
|
})
|
|
}
|
|
|
|
const isLoading = activeHooks.some((hook) => hook.isLoading)
|
|
|
|
return {
|
|
data: {
|
|
totalRequests: totalRequests.logData,
|
|
errorCounts: errorCounts.logData,
|
|
responseSpeed: responseSpeed.logData,
|
|
topRoutes: topRoutes.logData,
|
|
topErrorRoutes: topErrorRoutes.logData,
|
|
topSlowRoutes: topSlowRoutes.logData,
|
|
networkTraffic: networkTraffic.logData,
|
|
requestsByCountry: requestsByCountry.logData,
|
|
},
|
|
params: {
|
|
totalRequests: totalRequests.params,
|
|
errorCounts: errorCounts.params,
|
|
responseSpeed: responseSpeed.params,
|
|
topRoutes: topRoutes.params,
|
|
topErrorRoutes: topErrorRoutes.params,
|
|
topSlowRoutes: topSlowRoutes.params,
|
|
networkTraffic: networkTraffic.params,
|
|
requestsByCountry: requestsByCountry.params,
|
|
},
|
|
error: {
|
|
totalRequest: totalRequests.error,
|
|
errorCounts: errorCounts.error,
|
|
responseSpeed: responseSpeed.error,
|
|
topRoutes: topRoutes.error,
|
|
topErrorRoute: topErrorRoutes.error,
|
|
topSlowRoutes: topSlowRoutes.error,
|
|
networkTraffic: networkTraffic.error,
|
|
requestsByCountry: requestsByCountry.error,
|
|
},
|
|
mergeParams: handleSetParams,
|
|
filters,
|
|
addFilter,
|
|
removeFilter,
|
|
removeFilters,
|
|
isLoading,
|
|
refresh: handleRefresh,
|
|
}
|
|
}
|