Files
supabase/apps/studio/components/interfaces/App/CommandMenu/ContextSearchCommands.tsx
Joshen Lim aba4e147eb Joshen/fe 3613 database tables query should have schema filter wherever appropriate (#46935)
## 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 -->
2026-06-16 15:45:39 +08:00

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,
})
}