mirror of
https://github.com/supabase/supabase.git
synced 2026-09-08 19:08:44 +08:00
## 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 -->
165 lines
4.8 KiB
TypeScript
165 lines
4.8 KiB
TypeScript
'use client'
|
|
|
|
import { useDebounce } from '@uidotdev/usehooks'
|
|
import { IS_PLATFORM } from 'common'
|
|
import { Auth, EdgeFunctions, Storage } from 'icons'
|
|
import { Database } from 'lucide-react'
|
|
import { useMemo, useState } from 'react'
|
|
import type { ICommand } from 'ui-patterns/CommandMenu'
|
|
import {
|
|
CommandHeader,
|
|
CommandMenuInput,
|
|
CommandWrapper,
|
|
PageType,
|
|
useQuery,
|
|
useRegisterCommands,
|
|
useRegisterPage,
|
|
useSetPage,
|
|
} from 'ui-patterns/CommandMenu'
|
|
|
|
import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
|
|
import { ContextSearchResults } from './ContextSearchResults'
|
|
import { orderCommandSectionsByPriority } from './ordering'
|
|
import type { SearchContextValue } from './SearchContext.types'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
|
|
interface SearchContextOption {
|
|
value: SearchContextValue
|
|
label: string
|
|
pageName: string
|
|
placeholder: string
|
|
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
|
|
}
|
|
|
|
const SEARCH_CONTEXT_OPTIONS: SearchContextOption[] = [
|
|
{
|
|
value: 'database-tables',
|
|
label: 'Database Tables',
|
|
pageName: 'Search Database Tables',
|
|
placeholder: 'Search database schema or tables...',
|
|
icon: Database,
|
|
},
|
|
{
|
|
value: 'auth-policies',
|
|
label: 'RLS Policies',
|
|
pageName: 'Search RLS Policies',
|
|
placeholder: 'Search rls policies...',
|
|
icon: Auth,
|
|
},
|
|
{
|
|
value: 'edge-functions',
|
|
label: 'Edge Functions',
|
|
pageName: 'Search Edge Functions',
|
|
placeholder: 'Search edge functions...',
|
|
icon: EdgeFunctions,
|
|
},
|
|
{
|
|
value: 'storage',
|
|
label: 'Storage',
|
|
pageName: 'Search Storage',
|
|
placeholder: 'Search buckets...',
|
|
icon: Storage,
|
|
},
|
|
]
|
|
|
|
function ContextSearchPage({
|
|
context,
|
|
placeholder,
|
|
}: {
|
|
context: SearchContextValue
|
|
placeholder: string
|
|
}) {
|
|
const query = useQuery()
|
|
|
|
const [filterString, setFilterString] = useState('')
|
|
const debouncedFilterString = useDebounce(filterString, 300)
|
|
|
|
return (
|
|
<CommandWrapper shouldFilter={context !== 'database-tables'}>
|
|
<CommandHeader>
|
|
<CommandMenuInput
|
|
placeholder={placeholder}
|
|
value={filterString}
|
|
onValueChange={setFilterString}
|
|
/>
|
|
</CommandHeader>
|
|
<ContextSearchResults
|
|
context={context}
|
|
query={query}
|
|
debouncedFilterString={debouncedFilterString}
|
|
/>
|
|
</CommandWrapper>
|
|
)
|
|
}
|
|
|
|
export function useContextSearchCommands() {
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const setPage = useSetPage()
|
|
|
|
const {
|
|
projectAuthAll: authEnabled,
|
|
projectEdgeFunctionAll: edgeFunctionsEnabled,
|
|
projectStorageAll: storageEnabled,
|
|
} = useIsFeatureEnabled(['project_auth:all', 'project_edge_function:all', 'project_storage:all'])
|
|
|
|
const pageDefinitions = [
|
|
{ title: 'Search Database Tables', context: 'database-tables' as const },
|
|
{ title: 'Search RLS Policies', context: 'auth-policies' as const },
|
|
{ title: 'Search Edge Functions', context: 'edge-functions' as const },
|
|
{ title: 'Search Storage', context: 'storage' as const },
|
|
]
|
|
|
|
// Register pages - pageDefinitions is constant, so hooks are called in consistent order
|
|
for (const { title, context } of pageDefinitions) {
|
|
const placeholder =
|
|
SEARCH_CONTEXT_OPTIONS.find((opt) => opt.value === context)?.placeholder ?? ''
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
useRegisterPage(title, {
|
|
type: PageType.Component,
|
|
component: () => <ContextSearchPage context={context} placeholder={placeholder} />,
|
|
})
|
|
}
|
|
|
|
const contextCommands = useMemo(() => {
|
|
return SEARCH_CONTEXT_OPTIONS.filter((option) => {
|
|
let isFeatureEnabled = false
|
|
switch (option.value) {
|
|
case 'database-tables':
|
|
isFeatureEnabled = true
|
|
break
|
|
case 'auth-policies':
|
|
isFeatureEnabled = authEnabled
|
|
break
|
|
case 'edge-functions':
|
|
isFeatureEnabled = edgeFunctionsEnabled
|
|
break
|
|
case 'storage':
|
|
isFeatureEnabled = storageEnabled
|
|
break
|
|
}
|
|
|
|
if (!isFeatureEnabled) return false
|
|
|
|
// If self-hosted, show if feature is enabled
|
|
if (!IS_PLATFORM) {
|
|
return true
|
|
}
|
|
|
|
// only show when inside a project if not self-hosted
|
|
return !!project
|
|
}).map((option) => ({
|
|
id: `search-${option.value}`,
|
|
name: `Search ${option.label}...`,
|
|
action: () => setPage(option.pageName),
|
|
icon: () => <option.icon className="h-4 w-4" strokeWidth={1.5} />,
|
|
})) as ICommand[]
|
|
}, [setPage, authEnabled, edgeFunctionsEnabled, storageEnabled, project])
|
|
|
|
useRegisterCommands(COMMAND_MENU_SECTIONS.QUERY, contextCommands, {
|
|
orderSection: orderCommandSectionsByPriority,
|
|
sectionMeta: { priority: 3 },
|
|
enabled: !IS_PLATFORM || !!project,
|
|
})
|
|
}
|