ChooseFunctionForm.tsx173 lines · main
1import { useParams } from 'common'
2import { map as lodashMap, uniqBy } from 'lodash'
3import { HelpCircle, Terminal } from 'lucide-react'
4import Link from 'next/link'
5import { useRouter } from 'next/router'
6import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, SidePanel } from 'ui'
7
8import ProductEmptyState from '@/components/to-be-cleaned/ProductEmptyState'
9import InformationBox from '@/components/ui/InformationBox'
10import SqlEditor from '@/components/ui/SqlEditor'
11import {
12 useDatabaseFunctionsQuery,
13 type DatabaseFunction,
14} from '@/data/database-functions/database-functions-query'
15import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
16
17export interface ChooseFunctionFormProps {
18 visible: boolean
19 setVisible: (value: boolean) => void
20 onChange: (fn: DatabaseFunction) => void
21}
22
23const ChooseFunctionForm = ({ visible, onChange, setVisible }: ChooseFunctionFormProps) => {
24 const { data: project } = useSelectedProjectQuery()
25
26 const { data = [] } = useDatabaseFunctionsQuery({
27 projectRef: project?.ref,
28 connectionString: project?.connectionString,
29 })
30 const triggerFunctions = data.filter((fn) => fn.return_type === 'trigger')
31 const hasPublicSchemaFunctions = triggerFunctions.length >= 1
32 const functionSchemas = lodashMap(uniqBy(triggerFunctions, 'schema'), 'schema')
33
34 const selectFunction = (id: number) => {
35 const fn = triggerFunctions.find((x) => x.id === id)
36 if (!!fn) onChange(fn)
37 setVisible(!visible)
38 }
39
40 return (
41 <SidePanel
42 size="large"
43 header="Pick a function"
44 visible={visible}
45 onCancel={() => setVisible(!visible)}
46 className="hooks-sidepanel"
47 >
48 <div className="py-6">
49 {hasPublicSchemaFunctions ? (
50 <div className="space-y-6">
51 <NoticeBox />
52 {functionSchemas.map((schema: string) => (
53 <SchemaFunctionGroup
54 key={schema}
55 schema={schema}
56 functions={triggerFunctions.filter((x) => x.schema == schema)}
57 selectFunction={selectFunction}
58 />
59 ))}
60 </div>
61 ) : (
62 <NoFunctionsState />
63 )}
64 </div>
65 </SidePanel>
66 )
67}
68
69export default ChooseFunctionForm
70
71const NoticeBox = () => {
72 const { ref } = useParams()
73
74 return (
75 <div className="px-6">
76 <InformationBox
77 icon={<HelpCircle size="20" strokeWidth={1.5} />}
78 title="Only functions that return a trigger will be displayed below"
79 description={`You can make functions by using the Database Functions`}
80 button={
81 <Button asChild type="default">
82 <Link href={`/project/${ref}/database/functions`}>Go to Functions</Link>
83 </Button>
84 }
85 />
86 </div>
87 )
88}
89
90const NoFunctionsState = () => {
91 // for the empty 'no tables' state link
92 const router = useRouter()
93 const { ref } = router.query
94
95 return (
96 <ProductEmptyState
97 title="No Trigger Functions found in database"
98 ctaButtonLabel="Create a trigger function"
99 onClickCta={() => {
100 router.push(`/project/${ref}/database/functions`)
101 }}
102 >
103 <p className="text-sm text-foreground-light">
104 You will need to create a trigger based function before you can add it to your trigger.
105 </p>
106 </ProductEmptyState>
107 )
108}
109
110export interface SchemaFunctionGroupProps {
111 schema: string
112 functions: DatabaseFunction[]
113 selectFunction: (id: number) => void
114}
115
116const SchemaFunctionGroup = ({ schema, functions, selectFunction }: SchemaFunctionGroupProps) => {
117 return (
118 <div className="space-y-4">
119 <div className="sticky top-0 flex items-center space-x-1 px-6 backdrop-blur-sm backdrop-filter">
120 <h5 className="text-foreground-light">schema</h5>
121 <h5>{schema}</h5>
122 </div>
123 <div className="space-y-0 divide-y border-t border-b border-default">
124 {functions.map((x) => (
125 <Function
126 id={x.id}
127 key={x.id}
128 completeStatement={x.complete_statement}
129 name={x.name}
130 onClick={selectFunction}
131 />
132 ))}
133 </div>
134 </div>
135 )
136}
137
138export interface FunctionProps {
139 id: number
140 completeStatement: string
141 name: string
142 onClick: (id: number) => void
143}
144
145const Function = ({ id, completeStatement, name, onClick }: FunctionProps) => {
146 return (
147 <div className="cursor-pointer rounded-sm p-3 px-6 hover:bg-studio" onClick={() => onClick(id)}>
148 <Accordion type="single" collapsible>
149 <AccordionItem value="definition" className="border-none">
150 <div className="flex items-center justify-between space-x-3">
151 <div className="flex items-center space-x-3">
152 <div className="flex items-center justify-center rounded-sm bg-foreground p-1 text-background">
153 <Terminal strokeWidth={2} size={14} />
154 </div>
155 <p className="mb-0 text-sm">{name}</p>
156 </div>
157 <AccordionTrigger
158 onClick={(e) => e.stopPropagation()}
159 className="py-0 text-xs font-normal text-foreground-light hover:no-underline"
160 >
161 View definition
162 </AccordionTrigger>
163 </div>
164 <AccordionContent className="[&>div]:pb-0" onClick={(e) => e.stopPropagation()}>
165 <div className="mt-4 h-64 border border-default">
166 <SqlEditor defaultValue={completeStatement} readOnly={true} contextmenu={false} />
167 </div>
168 </AccordionContent>
169 </AccordionItem>
170 </Accordion>
171 </div>
172 )
173}