useReportFilters() — supabase Function Reference
Architecture documentation for the useReportFilters() function in useReportFilters.ts from the supabase codebase.
Entity Profile
Relationship Graph
Source Code
apps/studio/components/interfaces/Reports/useReportFilters.ts lines 27–327
export const useReportFilters = ({
onAddFilter,
onRemoveFilters,
filters,
}: UseReportFiltersProps) => {
// URL-safe operator mappings
const URL_OPERATOR_MAP = {
'=': 'eq',
'!=': 'neq',
'>=': 'gte',
'<=': 'lte',
'>': 'gt',
'<': 'lt',
CONTAINS: 'contains',
'STARTS WITH': 'startswith',
'ENDS WITH': 'endswith',
}
const REVERSE_URL_OPERATOR_MAP = Object.fromEntries(
Object.entries(URL_OPERATOR_MAP).map(([k, v]) => [v, k])
)
// Parse encoded filter value (e.g., "gte:300" -> { operator: ">=", value: "300" })
const parseFilterValue = useCallback((encodedValue: string | number | null) => {
if (!encodedValue || typeof encodedValue !== 'string') {
return { operator: '=', value: encodedValue?.toString() || '' }
}
const parts = encodedValue.split(':')
if (parts.length === 2) {
const [urlOperator, value] = parts
const operator = REVERSE_URL_OPERATOR_MAP[urlOperator] || '='
return { operator, value }
}
return { operator: '=', value: encodedValue }
}, [])
const encodeFilterValue = useCallback((operator: string | number, value: string | number) => {
const urlOperator = URL_OPERATOR_MAP[operator as keyof typeof URL_OPERATOR_MAP] || 'eq'
return `${urlOperator}:${value}`
}, [])
// Convert query filters to ReportFilter format
const convertQueryFiltersToReportFilters = useCallback(
(queryState: Record<string, string | number | null>): ReportFilter[] => {
const filters: ReportFilter[] = []
Object.entries(queryState).forEach(([key, value]) => {
if (value !== null && value !== undefined && value !== '') {
const { operator, value: filterValue } = parseFilterValue(value)
filters.push({
propertyName: key,
value: filterValue,
operator: operator,
})
}
})
return filters
},
[parseFilterValue]
)
// Convert operator from FilterBar format to ReportFilterItem format
const getCompareOperator = useCallback(
(operator: string | number): 'matches' | 'is' | '>=' | '<=' | '>' | '<' | '!=' => {
switch (operator) {
case '=':
return 'is'
case '!=':
return '!='
case '>=':
return '>='
case '<=':
return '<='
case '>':
return '>'
case '<':
return '<'
case 'CONTAINS':
case 'STARTS WITH':
case 'ENDS WITH':
return 'matches'
default:
return 'matches'
}
},
[]
)
// Convert ReportFilter to ReportFilterItem format for the report system
const convertReportFiltersToReportFilterItems = useMemo(
() =>
(reportFilters: ReportFilter[]): ReportFilterItem[] => {
const reportFilterItems: ReportFilterItem[] = []
reportFilters.forEach((filter) => {
if (filter.value !== null && filter.value !== '' && filter.value !== undefined) {
reportFilterItems.push({
key: filter.propertyName.toString(),
value: filter.value.toString(),
compare: getCompareOperator(filter.operator),
})
}
})
return reportFilterItems
},
[getCompareOperator]
)
// Convert ReportFilter back to query filters format
const convertReportFiltersToQueryFilters = useMemo(
() => (reportFilters: ReportFilter[]) => {
const queryUpdate: Record<string, string | number | null> = {}
// Clear all existing filters first
Object.keys(REPORT_FILTER_PARAMS_PARSER).forEach((key) => {
queryUpdate[key] = null
})
// Add new filters with encoded operator+value
reportFilters.forEach((filter) => {
if (filter.propertyName in REPORT_FILTER_PARAMS_PARSER) {
const encodedValue = encodeFilterValue(filter.operator, filter.value as string | number)
queryUpdate[filter.propertyName] = encodedValue
}
})
return queryUpdate
},
[encodeFilterValue]
)
const [queryFilters, setQueryFilters] = useQueryStates(REPORT_FILTER_PARAMS_PARSER)
const [localFilters, setLocalFilters] = useState<ReportFilter[]>([])
const [isInitialized, setIsInitialized] = useState(false)
const lastAppliedFilterState = useRef<string>('')
const preventUrlSync = useRef(false)
// Initialize local state from URL params (only once on mount)
useEffect(() => {
if (!isInitialized) {
const initialFilters = convertQueryFiltersToReportFilters(queryFilters)
setLocalFilters(initialFilters)
setIsInitialized(true)
}
}, [isInitialized, convertQueryFiltersToReportFilters, queryFilters])
// Sync URL changes back to local state (when URL changes externally)
useEffect(() => {
if (!isInitialized || preventUrlSync.current) return
const filtersFromUrl = convertQueryFiltersToReportFilters(queryFilters)
const currentFilterJson = JSON.stringify(localFilters)
const urlFilterJson = JSON.stringify(filtersFromUrl)
if (currentFilterJson !== urlFilterJson) {
console.log('Syncing local filters from URL change:', { filtersFromUrl, localFilters })
setLocalFilters(filtersFromUrl)
}
}, [queryFilters, isInitialized, convertQueryFiltersToReportFilters])
// Sync local filter changes back to URL state (only after initialization)
useEffect(() => {
if (!isInitialized) return
const queryUpdate = convertReportFiltersToQueryFilters(localFilters)
// Prevent feedback loop during URL updates
preventUrlSync.current = true
setQueryFilters(queryUpdate).finally(() => {
preventUrlSync.current = false
})
}, [localFilters, isInitialized, setQueryFilters, convertReportFiltersToQueryFilters])
// Separate effect for report filter updates (only for filters with values)
useEffect(() => {
if (!isInitialized) return
const reportFilterItems = convertReportFiltersToReportFilterItems(localFilters)
// Create a state string for comparison (only for the filters we manage)
const newFilterState = reportFilterItems
.map((filter) => `${filter.key}:${filter.value}:${filter.compare}`)
.sort()
.join('|')
if (lastAppliedFilterState.current !== newFilterState) {
lastAppliedFilterState.current = newFilterState
// Separate product filters (managed by dropdown) from other filters (managed by ReportFilterPopover)
const PRODUCT_FILTER_KEYS = ['request.path']
const PRODUCT_FILTER_VALUES = ['/rest', '/auth', '/storage', '/realtime', '/graphql']
const productFilters = filters.filter(
(f) =>
PRODUCT_FILTER_KEYS.includes(f.key) && PRODUCT_FILTER_VALUES.includes(f.value.toString())
)
const otherFilters = filters.filter(
(f) =>
!(
PRODUCT_FILTER_KEYS.includes(f.key) &&
PRODUCT_FILTER_VALUES.includes(f.value.toString())
)
)
console.log('Filter Update:', {
localFilters,
reportFilterItems,
productFilters,
otherFilters,
newFilterState,
})
// Remove only the non-product filters that we manage
if (otherFilters.length > 0) {
onRemoveFilters(otherFilters)
}
// Add all the new filters from the popover
reportFilterItems.forEach((filter) => onAddFilter(filter))
}
}, [localFilters, isInitialized])
const handleFilterChange = (newFilters: ReportFilter[]) => {
console.log('handleFilterChange called with:', newFilters)
setLocalFilters(newFilters)
}
// Get filter properties based on product type
const getFilterProperties = (): ReportFilterProperty[] => {
const baseProperties: ReportFilterProperty[] = [
{
label: 'Status Code',
name: ReportFilterKeys.STATUS_CODE,
type: 'number' as const,
options: [
{ label: '200', value: '200' },
{ label: '300', value: '300' },
{ label: '400', value: '400' },
{ label: '401', value: '401' },
{ label: '403', value: '403' },
{ label: '404', value: '404' },
{ label: '409', value: '409' },
{ label: '411', value: '411' },
{ label: '413', value: '413' },
{ label: '416', value: '416' },
{ label: '423', value: '423' },
{ label: '500', value: '500' },
{ label: '503', value: '503' },
{ label: '504', value: '504' },
],
operators: ['=', '!=', '>', '<', '>=', '<='],
placeholder: '200',
},
{
label: 'User Agent',
name: ReportFilterKeys.USER_AGENT,
type: 'string' as const,
operators: ['=', '!=', 'CONTAINS', 'STARTS WITH', 'ENDS WITH'],
placeholder:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
},
{
label: 'Search Params',
name: ReportFilterKeys.SEARCH,
type: 'string' as const,
operators: ['=', '!=', 'CONTAINS', 'STARTS WITH', 'ENDS WITH'],
placeholder: '?foo=bar',
},
{
label: 'Client Info',
name: ReportFilterKeys.X_CLIENT_INFO,
type: 'string' as const,
operators: ['=', '!=', 'CONTAINS', 'STARTS WITH', 'ENDS WITH'],
placeholder: 'supabase-js/1.0.0',
},
]
return [
{
label: 'Path',
name: ReportFilterKeys.PATH,
type: 'string' as const,
operators: ['=', '!=', 'CONTAINS', 'STARTS WITH', 'ENDS WITH'],
placeholder: '/rest/v1/tablename',
},
...baseProperties,
]
}
return {
localFilters,
filterProperties: getFilterProperties(),
handleFilterChange,
ReportFilterKeys,
}
}
Domain
Subdomains
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free