FunctionList.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import { PermissionAction } from '@supabase/shared-types/out/constants'
  2. import { includes, noop, sortBy } from 'lodash'
  3. import { Copy, Edit, Edit2, FileText, MoreVertical, Trash } from 'lucide-react'
  4. import Link from 'next/link'
  5. import { useRouter } from 'next/router'
  6. import {
  7. Button,
  8. DropdownMenu,
  9. DropdownMenuContent,
  10. DropdownMenuItem,
  11. DropdownMenuSeparator,
  12. DropdownMenuTrigger,
  13. TableCell,
  14. TableRow,
  15. } from 'ui'
  16. import { getDatabaseTriggersHref } from './FunctionList.utils'
  17. import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
  18. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  19. import type { DatabaseFunction } from '@/data/database-functions/database-functions-query'
  20. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  21. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  22. import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
  23. import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
  24. interface FunctionListProps {
  25. schema: string
  26. filterString: string
  27. isLocked: boolean
  28. returnTypeFilter: string[]
  29. securityFilter: string[]
  30. duplicateFunction: (fn: any) => void
  31. editFunction: (fn: any) => void
  32. deleteFunction: (fn: any) => void
  33. functions: DatabaseFunction[]
  34. }
  35. const FunctionList = ({
  36. schema,
  37. filterString,
  38. isLocked,
  39. returnTypeFilter,
  40. securityFilter,
  41. duplicateFunction = noop,
  42. editFunction = noop,
  43. deleteFunction = noop,
  44. functions,
  45. }: FunctionListProps) => {
  46. const router = useRouter()
  47. const { data: selectedProject } = useSelectedProjectQuery()
  48. const aiSnap = useAiAssistantStateSnapshot()
  49. const { openSidebar } = useSidebarManagerSnapshot()
  50. const filteredFunctions = (functions ?? []).filter((x) => {
  51. const matchesName = includes(x.name.toLowerCase(), filterString.toLowerCase())
  52. const matchesReturnType =
  53. returnTypeFilter.length === 0 || returnTypeFilter.includes(x.return_type)
  54. const matchesSecurity =
  55. securityFilter.length === 0 ||
  56. (securityFilter.includes('definer') && x.security_definer) ||
  57. (securityFilter.includes('invoker') && !x.security_definer)
  58. return matchesName && matchesReturnType && matchesSecurity
  59. })
  60. const _functions = sortBy(
  61. filteredFunctions.filter((x) => x.schema == schema),
  62. (func) => func.name.toLocaleLowerCase()
  63. )
  64. const projectRef = selectedProject?.ref
  65. const { can: canUpdateFunctions } = useAsyncCheckPermissions(
  66. PermissionAction.TENANT_SQL_ADMIN_WRITE,
  67. 'functions'
  68. )
  69. if (_functions.length === 0 && filterString.length === 0) {
  70. return (
  71. <TableRow key={schema}>
  72. <TableCell colSpan={5}>
  73. <p className="text-sm text-foreground">No functions created yet</p>
  74. <p className="text-sm text-foreground-light">
  75. There are no functions found in the schema "{schema}"
  76. </p>
  77. </TableCell>
  78. </TableRow>
  79. )
  80. }
  81. if (_functions.length === 0 && filterString.length > 0) {
  82. return (
  83. <TableRow key={schema}>
  84. <TableCell colSpan={5}>
  85. <p className="text-sm text-foreground">No results found</p>
  86. <p className="text-sm text-foreground-light">
  87. Your search for "{filterString}" did not return any results
  88. </p>
  89. </TableCell>
  90. </TableRow>
  91. )
  92. }
  93. return (
  94. <>
  95. {_functions.map((x) => {
  96. const isApiDocumentAvailable = schema == 'public' && x.return_type !== 'trigger'
  97. return (
  98. <TableRow key={x.id}>
  99. <TableCell className="truncate">
  100. <Button
  101. type="text"
  102. className="text-link-table-cell text-sm disabled:opacity-100 disabled:no-underline p-0 hover:bg-transparent title"
  103. disabled={isLocked || !canUpdateFunctions}
  104. onClick={() => editFunction(x)}
  105. title={x.name}
  106. >
  107. {x.name}
  108. </Button>
  109. </TableCell>
  110. <TableCell className="table-cell">
  111. <p
  112. title={x.argument_types}
  113. className={`truncate ${x.argument_types ? 'text-foreground-light' : 'text-foreground-muted'}`}
  114. >
  115. {x.argument_types || '–'}
  116. </p>
  117. </TableCell>
  118. <TableCell className="table-cell">
  119. {x.return_type === 'trigger' ? (
  120. <Link
  121. href={getDatabaseTriggersHref(projectRef, x.name)}
  122. className="truncate text-link"
  123. title={x.return_type}
  124. >
  125. {x.return_type}
  126. </Link>
  127. ) : (
  128. <p title={x.return_type} className="truncate text-foreground-light">
  129. {x.return_type}
  130. </p>
  131. )}
  132. </TableCell>
  133. <TableCell className="table-cell">
  134. <p className="truncate text-foreground-light">
  135. {x.security_definer ? 'Definer' : 'Invoker'}
  136. </p>
  137. </TableCell>
  138. <TableCell className="text-right">
  139. {!isLocked && (
  140. <div className="flex items-center justify-end">
  141. {canUpdateFunctions ? (
  142. <DropdownMenu>
  143. <DropdownMenuTrigger asChild>
  144. <Button
  145. aria-label="More options"
  146. type="default"
  147. className="px-1"
  148. icon={<MoreVertical />}
  149. />
  150. </DropdownMenuTrigger>
  151. <DropdownMenuContent side="left" className="w-52">
  152. {isApiDocumentAvailable && (
  153. <DropdownMenuItem
  154. className="space-x-2"
  155. onClick={() => router.push(`/project/${projectRef}/api?rpc=${x.name}`)}
  156. >
  157. <FileText size={14} />
  158. <p>Client API docs</p>
  159. </DropdownMenuItem>
  160. )}
  161. <DropdownMenuSeparator />
  162. <DropdownMenuItem className="space-x-2" onClick={() => editFunction(x)}>
  163. <Edit2 size={14} />
  164. <p>Edit function</p>
  165. </DropdownMenuItem>
  166. <DropdownMenuItem
  167. className="space-x-2"
  168. onClick={() => {
  169. openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
  170. aiSnap.newChat({
  171. name: `Update function ${x.name}`,
  172. initialInput: 'Update this function to do...',
  173. suggestions: {
  174. title:
  175. 'I can help you make a change to this function, here are a few example prompts to get you started:',
  176. prompts: [
  177. {
  178. label: 'Rename Function',
  179. description: 'Rename this function to ...',
  180. },
  181. {
  182. label: 'Modify Function',
  183. description: 'Modify this function so that it ...',
  184. },
  185. {
  186. label: 'Add Trigger',
  187. description:
  188. 'Add a trigger for this function that calls it when ...',
  189. },
  190. ],
  191. },
  192. sqlSnippets: [x.complete_statement],
  193. })
  194. }}
  195. >
  196. <Edit size={14} />
  197. <p>Edit function with Assistant</p>
  198. </DropdownMenuItem>
  199. <DropdownMenuItem
  200. className="space-x-2"
  201. onClick={() => duplicateFunction(x)}
  202. >
  203. <Copy size={14} />
  204. <p>Duplicate function</p>
  205. </DropdownMenuItem>
  206. <DropdownMenuSeparator />
  207. <DropdownMenuItem className="space-x-2" onClick={() => deleteFunction(x)}>
  208. <Trash size={14} className="text-destructive" />
  209. <p>Delete function</p>
  210. </DropdownMenuItem>
  211. </DropdownMenuContent>
  212. </DropdownMenu>
  213. ) : (
  214. <ButtonTooltip
  215. disabled
  216. type="default"
  217. icon={<MoreVertical />}
  218. className="px-1"
  219. tooltip={{
  220. content: {
  221. side: 'left',
  222. text: 'You need additional permissions to update functions',
  223. },
  224. }}
  225. />
  226. )}
  227. </div>
  228. )}
  229. </TableCell>
  230. </TableRow>
  231. )
  232. })}
  233. </>
  234. )
  235. }
  236. export default FunctionList