ChooseFunctionForm.tsx173 lines · main
| 1 | import { useParams } from 'common' |
| 2 | import { map as lodashMap, uniqBy } from 'lodash' |
| 3 | import { HelpCircle, Terminal } from 'lucide-react' |
| 4 | import Link from 'next/link' |
| 5 | import { useRouter } from 'next/router' |
| 6 | import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, SidePanel } from 'ui' |
| 7 | |
| 8 | import ProductEmptyState from '@/components/to-be-cleaned/ProductEmptyState' |
| 9 | import InformationBox from '@/components/ui/InformationBox' |
| 10 | import SqlEditor from '@/components/ui/SqlEditor' |
| 11 | import { |
| 12 | useDatabaseFunctionsQuery, |
| 13 | type DatabaseFunction, |
| 14 | } from '@/data/database-functions/database-functions-query' |
| 15 | import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' |
| 16 | |
| 17 | export interface ChooseFunctionFormProps { |
| 18 | visible: boolean |
| 19 | setVisible: (value: boolean) => void |
| 20 | onChange: (fn: DatabaseFunction) => void |
| 21 | } |
| 22 | |
| 23 | const 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 | |
| 69 | export default ChooseFunctionForm |
| 70 | |
| 71 | const 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 | |
| 90 | const 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 | |
| 110 | export interface SchemaFunctionGroupProps { |
| 111 | schema: string |
| 112 | functions: DatabaseFunction[] |
| 113 | selectFunction: (id: number) => void |
| 114 | } |
| 115 | |
| 116 | const 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 | |
| 138 | export interface FunctionProps { |
| 139 | id: number |
| 140 | completeStatement: string |
| 141 | name: string |
| 142 | onClick: (id: number) => void |
| 143 | } |
| 144 | |
| 145 | const 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 | } |