Skip to content

Commit 7ade636

Browse files
authored
Improve database functions list (supabase#39835)
1 parent cbf676c commit 7ade636

File tree

4 files changed

+88
-11
lines changed

4 files changed

+88
-11
lines changed

apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { PermissionAction } from '@supabase/shared-types/out/constants'
22
import { includes, noop, sortBy } from 'lodash'
33
import { Copy, Edit, Edit2, FileText, MoreVertical, Trash } from 'lucide-react'
4+
import Link from 'next/link'
45
import { useRouter } from 'next/router'
56

67
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
@@ -23,6 +24,8 @@ interface FunctionListProps {
2324
schema: string
2425
filterString: string
2526
isLocked: boolean
27+
returnTypeFilter: string[]
28+
securityFilter: string[]
2629
duplicateFunction: (fn: any) => void
2730
editFunction: (fn: any) => void
2831
deleteFunction: (fn: any) => void
@@ -32,6 +35,8 @@ const FunctionList = ({
3235
schema,
3336
filterString,
3437
isLocked,
38+
returnTypeFilter,
39+
securityFilter,
3540
duplicateFunction = noop,
3641
editFunction = noop,
3742
deleteFunction = noop,
@@ -45,9 +50,16 @@ const FunctionList = ({
4550
connectionString: selectedProject?.connectionString,
4651
})
4752

48-
const filteredFunctions = (functions ?? []).filter((x) =>
49-
includes(x.name.toLowerCase(), filterString.toLowerCase())
50-
)
53+
const filteredFunctions = (functions ?? []).filter((x) => {
54+
const matchesName = includes(x.name.toLowerCase(), filterString.toLowerCase())
55+
const matchesReturnType =
56+
returnTypeFilter.length === 0 || returnTypeFilter.includes(x.return_type)
57+
const matchesSecurity =
58+
securityFilter.length === 0 ||
59+
(securityFilter.includes('definer') && x.security_definer) ||
60+
(securityFilter.includes('invoker') && !x.security_definer)
61+
return matchesName && matchesReturnType && matchesSecurity
62+
})
5163
const _functions = sortBy(
5264
filteredFunctions.filter((x) => x.schema == schema),
5365
(func) => func.name.toLocaleLowerCase()
@@ -100,16 +112,30 @@ const FunctionList = ({
100112
{x.name}
101113
</Button>
102114
</TableCell>
103-
<TableCell className="table-cell overflow-auto">
104-
<p title={x.argument_types} className="truncate">
115+
<TableCell className="table-cell">
116+
<p title={x.argument_types} className="truncate text-foreground-light">
105117
{x.argument_types || '-'}
106118
</p>
107119
</TableCell>
108120
<TableCell className="table-cell">
109-
<p title={x.return_type}>{x.return_type}</p>
121+
{x.return_type === 'trigger' ? (
122+
<Link
123+
href={`/project/${projectRef}/database/triggers?search=${x.name}`}
124+
className="truncate text-link"
125+
title={x.return_type}
126+
>
127+
{x.return_type}
128+
</Link>
129+
) : (
130+
<p title={x.return_type} className="truncate text-foreground-light">
131+
{x.return_type}
132+
</p>
133+
)}
110134
</TableCell>
111135
<TableCell className="table-cell">
112-
{x.security_definer ? 'Definer' : 'Invoker'}
136+
<p className="truncate text-foreground-light">
137+
{x.security_definer ? 'Definer' : 'Invoker'}
138+
</p>
113139
</TableCell>
114140
<TableCell className="text-right">
115141
{!isLocked && (

apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
33
import { noop } from 'lodash'
44
import { Search } from 'lucide-react'
55
import { useRouter } from 'next/router'
6+
import { parseAsJson, useQueryState } from 'nuqs'
67

78
import { useParams } from 'common'
89
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
@@ -27,6 +28,10 @@ import {
2728
TableHeader,
2829
TableRow,
2930
} from 'ui'
31+
import {
32+
ReportsSelectFilter,
33+
selectFilterSchema,
34+
} from 'components/interfaces/Reports/v2/ReportsSelectFilter'
3035
import { ProtectedSchemaWarning } from '../../ProtectedSchemaWarning'
3136
import FunctionList from './FunctionList'
3237

@@ -51,6 +56,16 @@ const FunctionsList = ({
5156

5257
const filterString = search ?? ''
5358

59+
// Filters
60+
const [returnTypeFilter, setReturnTypeFilter] = useQueryState(
61+
'return_type',
62+
parseAsJson(selectFilterSchema.parse)
63+
)
64+
const [securityFilter, setSecurityFilter] = useQueryState(
65+
'security',
66+
parseAsJson(selectFilterSchema.parse)
67+
)
68+
5469
const setFilterString = (str: string) => {
5570
const url = new URL(document.URL)
5671
if (str === '') {
@@ -84,6 +99,18 @@ const FunctionsList = ({
8499
connectionString: project?.connectionString,
85100
})
86101

102+
// Get unique return types from functions in the selected schema
103+
const schemaFunctions = (functions ?? []).filter((fn) => fn.schema === selectedSchema)
104+
const uniqueReturnTypes = Array.from(new Set(schemaFunctions.map((fn) => fn.return_type))).sort()
105+
106+
// Get security options based on what exists in the selected schema
107+
const hasDefiner = schemaFunctions.some((fn) => fn.security_definer)
108+
const hasInvoker = schemaFunctions.some((fn) => !fn.security_definer)
109+
const securityOptions = [
110+
...(hasDefiner ? [{ label: 'Definer', value: 'definer' }] : []),
111+
...(hasInvoker ? [{ label: 'Invoker', value: 'invoker' }] : []),
112+
]
113+
87114
if (isLoading) return <GenericSkeletonLoader />
88115
if (isError) return <AlertError error={error} subject="Failed to retrieve database functions" />
89116

@@ -132,6 +159,22 @@ const FunctionsList = ({
132159
className="w-full lg:w-52"
133160
onChange={(e) => setFilterString(e.target.value)}
134161
/>
162+
<ReportsSelectFilter
163+
label="Return Type"
164+
options={uniqueReturnTypes.map((type) => ({
165+
label: type,
166+
value: type,
167+
}))}
168+
value={returnTypeFilter ?? []}
169+
onChange={setReturnTypeFilter}
170+
showSearch
171+
/>
172+
<ReportsSelectFilter
173+
label="Security"
174+
options={securityOptions}
175+
value={securityFilter ?? []}
176+
onChange={setSecurityFilter}
177+
/>
135178
</div>
136179

137180
<div className="flex items-center gap-x-2">
@@ -201,6 +244,8 @@ const FunctionsList = ({
201244
schema={selectedSchema}
202245
filterString={filterString}
203246
isLocked={isSchemaLocked}
247+
returnTypeFilter={returnTypeFilter ?? []}
248+
securityFilter={securityFilter ?? []}
204249
duplicateFunction={duplicateFunction}
205250
editFunction={editFunction}
206251
deleteFunction={deleteFunction}

apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ const TriggerList = ({
4444
projectRef: project?.ref,
4545
connectionString: project?.connectionString,
4646
})
47-
const filteredTriggers = (triggers ?? []).filter((x) =>
48-
includes(x.name.toLowerCase(), filterString.toLowerCase())
47+
const filteredTriggers = (triggers ?? []).filter(
48+
(x) =>
49+
includes(x.name.toLowerCase(), filterString.toLowerCase()) ||
50+
(x.function_name && includes(x.function_name.toLowerCase(), filterString.toLowerCase()))
4951
)
5052

5153
const _triggers = sortBy(

apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { PostgresTrigger } from '@supabase/postgres-meta'
22
import { PermissionAction } from '@supabase/shared-types/out/constants'
33
import { noop } from 'lodash'
44
import { DatabaseZap, FunctionSquare, Plus, Search, Shield } from 'lucide-react'
5-
import { useState } from 'react'
5+
import { parseAsString, useQueryState } from 'nuqs'
66

77
import AlphaPreview from 'components/to-be-cleaned/AlphaPreview'
88
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
@@ -48,7 +48,11 @@ const TriggersList = ({
4848
const { data: project } = useSelectedProjectQuery()
4949
const aiSnap = useAiAssistantStateSnapshot()
5050
const { selectedSchema, setSelectedSchema } = useQuerySchemaState()
51-
const [filterString, setFilterString] = useState<string>('')
51+
52+
const [filterString, setFilterString] = useQueryState(
53+
'search',
54+
parseAsString.withDefault('').withOptions({ history: 'replace', clearOnDefault: true })
55+
)
5256

5357
const { data: protectedSchemas } = useProtectedSchemas()
5458
const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema })

0 commit comments

Comments
 (0)