mirror of
https://github.com/supabase/supabase.git
synced 2026-07-26 17:32:31 -04:00
aba4e147eb
## Context There's certain areas in the dashboard where we're calling `useTablesQuery` without a schema filter, in which case the dashboard then fires a query against the project's database to fetch _all_ tables across _all_ schemas - this could easily be a heavy query if there's a large number of relations in the project's database. Am hence opting to either add a schema filter if appropriate, or otherwise opt to use the infinite loading behaviour ## Changes involved - Add schema filter to `useTablesQuery` in database triggers and publications - Use infinite loading for tables in Cmd K for "Run query on table" and "Search database tables" ## To test - [x] Verify that database triggers + publications still function as expected - [x] Verify that CMD K "Run query on table" and "Search database tables" still function as expected (including search) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Implemented debounced infinite-scrolling table search in the command menu and SQL editor command flow. * Added a schema selector dropdown to publications management for easier navigation. * **Improvements** * Removed the “Schema” column from the publications tables UI. * Updated search guidance and table-picker status (counts/loading) during infinite browsing. * Trigger table listings now follow the selected schema context. * Refined command menu list height and improved the database-tables placeholder text. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
135 lines
4.2 KiB
TypeScript
135 lines
4.2 KiB
TypeScript
import { useIntersectionObserver } from '@uidotdev/usehooks'
|
|
import { useParams } from 'common'
|
|
import { Database, Loader2 } from 'lucide-react'
|
|
import { useEffect, useMemo } from 'react'
|
|
|
|
import {
|
|
EmptyState,
|
|
ResultsList,
|
|
SkeletonResults,
|
|
type SearchResult,
|
|
} from './ContextSearchResults.shared'
|
|
import { useInfiniteTablesQuery } from '@/data/tables/tables-query'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
|
|
interface TableSearchResultsProps {
|
|
debouncedFilterString: string
|
|
}
|
|
|
|
export function TableSearchResults({ debouncedFilterString }: TableSearchResultsProps) {
|
|
const { ref: projectRef } = useParams()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
|
|
const [sentinelRef, sentinelEntry] = useIntersectionObserver({
|
|
threshold: 0,
|
|
rootMargin: '200px 0px 200px 0px',
|
|
})
|
|
|
|
const {
|
|
data: tablesData,
|
|
isSuccess,
|
|
isError: isErrorTables,
|
|
isPending: isLoadingTables,
|
|
hasNextPage: hasNextTablesPage,
|
|
isFetchingNextPage: isFetchingNextTablesPage,
|
|
fetchNextPage: fetchNextTablesPage,
|
|
} = useInfiniteTablesQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
includeColumns: false,
|
|
pageSize: 50,
|
|
nameFilter: debouncedFilterString,
|
|
})
|
|
|
|
const tables = useMemo(() => tablesData?.pages.flat() ?? [], [tablesData])
|
|
|
|
const tableResults: SearchResult[] = useMemo(() => {
|
|
if (!tables) return []
|
|
|
|
return tables.map((table) => {
|
|
const displayName =
|
|
table.schema && table.schema !== 'public'
|
|
? `${table.schema}.${table.name}`
|
|
: table.name || 'Untitled Table'
|
|
|
|
const description = table.comment
|
|
? table.comment.length > 50
|
|
? `${table.comment.slice(0, 50)}...`
|
|
: table.comment
|
|
: undefined
|
|
|
|
return {
|
|
id: String(table.id),
|
|
name: displayName,
|
|
description,
|
|
}
|
|
})
|
|
}, [tables])
|
|
|
|
const totalTables = tables?.length ?? 0
|
|
|
|
useEffect(() => {
|
|
if (
|
|
sentinelEntry?.isIntersecting &&
|
|
hasNextTablesPage &&
|
|
!isFetchingNextTablesPage &&
|
|
isSuccess
|
|
) {
|
|
fetchNextTablesPage()
|
|
}
|
|
}, [
|
|
isSuccess,
|
|
sentinelEntry?.isIntersecting,
|
|
hasNextTablesPage,
|
|
isFetchingNextTablesPage,
|
|
fetchNextTablesPage,
|
|
])
|
|
|
|
return (
|
|
<div className="relative h-full flex flex-col">
|
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
{isLoadingTables ? (
|
|
<SkeletonResults />
|
|
) : isErrorTables ? (
|
|
<div className="h-full flex flex-col items-center justify-center py-12 px-4 gap-4 text-center text-foreground-lighter">
|
|
<Database className="h-6 w-6" strokeWidth={1.5} />
|
|
<p className="text-sm">Failed to load tables</p>
|
|
</div>
|
|
) : tableResults.length === 0 ? (
|
|
<EmptyState icon={Database} label="Database Tables" query={debouncedFilterString} />
|
|
) : (
|
|
<>
|
|
<ResultsList
|
|
results={tableResults}
|
|
icon={Database}
|
|
getRoute={(result) => {
|
|
const table = tables?.find((t) => String(t.id) === result.id)
|
|
if (!table || !projectRef) return `/project/${projectRef}/editor` as `/${string}`
|
|
|
|
const schemaParam = table.schema ? `?schema=${table.schema}` : ''
|
|
return `/project/${projectRef}/editor/${table.id}${schemaParam}` as `/${string}`
|
|
}}
|
|
infiniteLoadingObserverRef={sentinelRef}
|
|
className="pb-9"
|
|
/>
|
|
<div className="absolute bottom-0 left-0 right-0 flex items-center justify-between min-h-9 h-9 px-4 border-t bg-surface-200 text-xs text-foreground-light z-10">
|
|
<div className="flex items-center gap-x-2">
|
|
{isLoadingTables ? (
|
|
<span className="flex items-center gap-2">
|
|
<Loader2 size={14} className="animate-spin" /> Loading...
|
|
</span>
|
|
) : (
|
|
<span>
|
|
Total: {totalTables.toLocaleString()} table{totalTables !== 1 ? 's' : ''}
|
|
{hasNextTablesPage ? ' loaded' : ''}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|