add http parameters

This commit is contained in:
Danny White committed 2026-03-23 11:21:54 +11:00
1 parent ee4417422b
commit df9d4b1814
5 files changed
+150 -124

No files matched your search

@@ -0,0 +1,51 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useForm } from 'react-hook-form'
import { describe, expect, it } from 'vitest'
import { Form_Shadcn_ } from 'ui'
import { CreateFunctionConfigParamsSection } from './CreateFunctionConfigParamsSection'
type ConfigParamsFormValues = {
config_params: Array<{ name: string; value: string }>
}
const CreateFunctionConfigParamsHarness = () => {
const form = useForm<ConfigParamsFormValues>({
defaultValues: {
config_params: [{ name: 'search_path', value: 'public' }],
},
})
return (
<Form_Shadcn_ {...form}>
<CreateFunctionConfigParamsSection />
</Form_Shadcn_>
)
}
describe('CreateFunctionConfigParamsSection', () => {
it('appends a new config parameter row with name and value fields', async () => {
const user = userEvent.setup()
render(<CreateFunctionConfigParamsHarness />)
await user.click(screen.getByRole('button', { name: 'Add a new config' }))
expect(screen.getAllByPlaceholderText('parameter_name')).toHaveLength(2)
expect(screen.getAllByPlaceholderText('parameter_value')).toHaveLength(2)
})
it('removes an existing config parameter row', async () => {
const user = userEvent.setup()
render(<CreateFunctionConfigParamsHarness />)
expect(screen.getByDisplayValue('search_path')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'Remove configuration parameter' }))
expect(screen.queryByDisplayValue('search_path')).not.toBeInTheDocument()
})
})
@@ -0,0 +1,28 @@
import { useFormContext } from 'react-hook-form'
import { KeyValueFieldArray } from 'ui-patterns/form/KeyValueFieldArray/KeyValueFieldArray'
type CreateFunctionConfigParamsFormValues = {
config_params: Array<{ name: string; value: string }>
}
export const CreateFunctionConfigParamsSection = () => {
const form = useFormContext<CreateFunctionConfigParamsFormValues>()
return (
<>
<h5 className="text-base text-foreground">Configuration Parameters</h5>
<KeyValueFieldArray
control={form.control}
name="config_params"
keyFieldName="name"
valueFieldName="value"
createEmptyRow={() => ({ name: '', value: '' })}
keyPlaceholder="parameter_name"
valuePlaceholder="parameter_value"
addLabel="Add a new config"
removeLabel="Remove configuration parameter"
rowsClassName="space-y-2 pt-4"
/>
</>
)
}
@@ -44,6 +44,7 @@ import {
import { DiscardChangesConfirmationDialog } from 'components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { convertArgumentTypes, convertConfigParams } from '../Functions.utils'
import { CreateFunctionConfigParamsSection } from './CreateFunctionConfigParamsSection'
import { CreateFunctionHeader } from './CreateFunctionHeader'
import { FunctionEditor } from './FunctionEditor'
@@ -331,7 +332,7 @@ export const CreateFunction = ({
</SheetSection>
<Separator className={focusedEditor ? 'hidden' : ''} />
<SheetSection className={focusedEditor ? 'hidden' : ''}>
<FormFieldConfigParams readonly={isEditing} />
<CreateFunctionConfigParamsSection />
</SheetSection>
<Separator className={focusedEditor ? 'hidden' : ''} />
<SheetSection className={focusedEditor ? 'hidden' : ''}>
@@ -505,75 +506,6 @@ const FormFieldArgs = ({ readonly }: FormFieldConfigParamsProps) => {
)
}
interface FormFieldConfigParamsProps {
readonly?: boolean
}
const FormFieldConfigParams = ({ readonly }: FormFieldConfigParamsProps) => {
const { fields, append, remove } = useFieldArray<z.infer<typeof FormSchema>>({
name: 'config_params',
})
return (
<>
<h5 className="text-base text-foreground">Configuration Parameters</h5>
<div className="space-y-2 pt-4">
{readonly && isEmpty(fields) && (
<span className="text-foreground-lighter">No argument for this function</span>
)}
{fields.map((field, index) => {
return (
<div className="flex flex-row space-x-1" key={field.id}>
<FormField_Shadcn_
name={`config_params.${index}.name`}
render={({ field }) => (
<FormItem_Shadcn_ className="flex-1">
<FormControl_Shadcn_>
<Input_Shadcn_ {...field} placeholder="parameter_name" />
</FormControl_Shadcn_>
<FormMessage_Shadcn_ />
</FormItem_Shadcn_>
)}
/>
<FormField_Shadcn_
name={`config_params.${index}.value`}
render={({ field }) => (
<FormItem_Shadcn_ className="flex-1">
<FormControl_Shadcn_>
<Input_Shadcn_ {...field} placeholder="parameter_value" />
</FormControl_Shadcn_>
<FormMessage_Shadcn_ />
</FormItem_Shadcn_>
)}
/>
{!readonly && (
<Button
type="danger"
icon={<Trash size={12} />}
onClick={() => remove(index)}
className="h-[38px] w-[38px]"
/>
)}
</div>
)
})}
{!readonly && (
<Button
type="default"
icon={<Plus size={12} />}
onClick={() => append({ name: '', type: '' })}
disabled={readonly}
>
Add a new config
</Button>
)}
</div>
</>
)
}
const ALL_ALLOWED_LANGUAGES = ['plpgsql', 'sql', 'plcoffee', 'plv8', 'plls']
const FormFieldLanguage = () => {
@@ -0,0 +1,56 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useForm } from 'react-hook-form'
import { describe, expect, it } from 'vitest'
import { Form_Shadcn_ } from 'ui'
import { type WebhookFormValues } from './EditHookPanel.constants'
import { HTTPParameters } from './HTTPParameters'
const HTTPParametersHarness = () => {
const form = useForm<WebhookFormValues>({
defaultValues: {
name: 'test-hook',
table_id: 'public.messages',
http_method: 'POST',
timeout_ms: 1000,
events: ['INSERT'],
function_type: 'http_request',
http_url: 'https://hooks.example.com/webhook',
httpHeaders: [],
httpParameters: [{ id: 'param-1', name: 'tenant', value: 'prod' }],
},
})
return (
<Form_Shadcn_ {...form}>
<HTTPParameters form={form} />
</Form_Shadcn_>
)
}
describe('HTTPParameters', () => {
it('appends a new parameter row', async () => {
const user = userEvent.setup()
render(<HTTPParametersHarness />)
await user.click(screen.getByRole('button', { name: 'Add a new parameter' }))
expect(screen.getAllByPlaceholderText('Parameter name')).toHaveLength(2)
expect(screen.getAllByPlaceholderText('Parameter value')).toHaveLength(2)
})
it('removes an existing parameter row', async () => {
const user = userEvent.setup()
render(<HTTPParametersHarness />)
expect(screen.getByDisplayValue('tenant')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'Remove parameter' }))
expect(screen.queryByDisplayValue('tenant')).not.toBeInTheDocument()
})
})
@@ -1,9 +1,7 @@
import { Plus, Trash } from 'lucide-react'
import { useFieldArray, UseFormReturn } from 'react-hook-form'
import { Button, FormControl_Shadcn_, FormField_Shadcn_, Input_Shadcn_, SidePanel } from 'ui'
import { UseFormReturn } from 'react-hook-form'
import { KeyValueFieldArray } from 'ui-patterns/form/KeyValueFieldArray/KeyValueFieldArray'
import { WebhookFormValues } from './EditHookPanel.constants'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import {
FormSection,
FormSectionContent,
@@ -16,61 +14,22 @@ interface HTTPParametersProps {
}
export const HTTPParameters = ({ form }: HTTPParametersProps) => {
const {
fields: paramFields,
append: appendParam,
remove: removeParam,
} = useFieldArray({
control: form.control,
name: 'httpParameters',
})
return (
<FormSection
header={<FormSectionLabel className="lg:!col-span-4">HTTP Parameters</FormSectionLabel>}
>
<FormSectionContent loading={false} className="lg:!col-span-8">
<div className="space-y-2">
{paramFields.map((field, index) => (
<div key={field.id} className="flex items-center space-x-2">
<FormField_Shadcn_
control={form.control}
name={`httpParameters.${index}.name`}
render={({ field }) => (
<FormControl_Shadcn_>
<Input_Shadcn_ {...field} className="w-full" placeholder="Parameter name" />
</FormControl_Shadcn_>
)}
/>
<FormField_Shadcn_
control={form.control}
name={`httpParameters.${index}.value`}
render={({ field }) => (
<FormControl_Shadcn_>
<Input_Shadcn_ {...field} className="w-full" placeholder="Parameter value" />
</FormControl_Shadcn_>
)}
/>
<ButtonTooltip
type="text"
className="py-4"
icon={<Trash />}
onClick={() => removeParam(index)}
tooltip={{ content: { side: 'bottom', text: 'Remove parameter' } }}
/>
</div>
))}
<div>
<Button
type="default"
size="tiny"
icon={<Plus />}
onClick={() => appendParam({ id: uuidv4(), name: '', value: '' })}
>
Add a new parameter
</Button>
</div>
</div>
<KeyValueFieldArray
control={form.control}
name="httpParameters"
keyFieldName="name"
valueFieldName="value"
createEmptyRow={() => ({ id: uuidv4(), name: '', value: '' })}
keyPlaceholder="Parameter name"
valuePlaceholder="Parameter value"
addLabel="Add a new parameter"
removeLabel="Remove parameter"
/>
</FormSectionContent>
</FormSection>
)