♻️ (builder) Change to features-centric folder structure
This commit is contained in:
committed by
Baptiste Arnaud
parent
3686465a85
commit
643571fe7d
@@ -0,0 +1,20 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { WebhookBlock } from 'models'
|
||||
import { byId } from 'utils'
|
||||
|
||||
type Props = {
|
||||
block: WebhookBlock
|
||||
}
|
||||
|
||||
export const WebhookContent = ({ block: { webhookId } }: Props) => {
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(webhookId))
|
||||
|
||||
if (!webhook?.url) return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
<Text noOfLines={2} pr="6">
|
||||
{webhook.method} {webhook.url}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WebhookIcon as WebhookIco } from '@/components/icons'
|
||||
import { IconProps } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
|
||||
export const WebhookIcon = (props: IconProps) => <WebhookIco {...props} />
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Input } from '@/components/inputs'
|
||||
import { TableListItemProps } from '@/components/TableList'
|
||||
import { Stack, FormControl, FormLabel } from '@chakra-ui/react'
|
||||
import { KeyValue } from 'models'
|
||||
|
||||
export const QueryParamsInputs = (props: TableListItemProps<KeyValue>) => (
|
||||
<KeyValueInputs
|
||||
{...props}
|
||||
keyPlaceholder="e.g. email"
|
||||
valuePlaceholder="e.g. {{Email}}"
|
||||
/>
|
||||
)
|
||||
|
||||
export const HeadersInputs = (props: TableListItemProps<KeyValue>) => (
|
||||
<KeyValueInputs
|
||||
{...props}
|
||||
keyPlaceholder="e.g. Content-Type"
|
||||
valuePlaceholder="e.g. application/json"
|
||||
/>
|
||||
)
|
||||
|
||||
export const KeyValueInputs = ({
|
||||
item,
|
||||
onItemChange,
|
||||
keyPlaceholder,
|
||||
valuePlaceholder,
|
||||
debounceTimeout,
|
||||
}: TableListItemProps<KeyValue> & {
|
||||
keyPlaceholder?: string
|
||||
valuePlaceholder?: string
|
||||
}) => {
|
||||
const handleKeyChange = (key: string) => {
|
||||
if (key === item.key) return
|
||||
onItemChange({ ...item, key })
|
||||
}
|
||||
const handleValueChange = (value: string) => {
|
||||
if (value === item.value) return
|
||||
onItemChange({ ...item, value })
|
||||
}
|
||||
return (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor={'key' + item.id}>Key:</FormLabel>
|
||||
<Input
|
||||
id={'key' + item.id}
|
||||
defaultValue={item.key ?? ''}
|
||||
onChange={handleKeyChange}
|
||||
placeholder={keyPlaceholder}
|
||||
debounceTimeout={debounceTimeout}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor={'value' + item.id}>Value:</FormLabel>
|
||||
<Input
|
||||
id={'value' + item.id}
|
||||
defaultValue={item.value ?? ''}
|
||||
onChange={handleValueChange}
|
||||
placeholder={valuePlaceholder}
|
||||
debounceTimeout={debounceTimeout}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { SearchableDropdown } from '@/components/SearchableDropdown'
|
||||
import { TableListItemProps } from '@/components/TableList'
|
||||
import { VariableSearchInput } from '@/components/VariableSearchInput'
|
||||
import { Stack, FormControl, FormLabel } from '@chakra-ui/react'
|
||||
import { Variable, ResponseVariableMapping } from 'models'
|
||||
|
||||
export const DataVariableInputs = ({
|
||||
item,
|
||||
onItemChange,
|
||||
dataItems,
|
||||
debounceTimeout,
|
||||
}: TableListItemProps<ResponseVariableMapping> & { dataItems: string[] }) => {
|
||||
const handleBodyPathChange = (bodyPath: string) =>
|
||||
onItemChange({ ...item, bodyPath })
|
||||
const handleVariableChange = (variable?: Variable) =>
|
||||
onItemChange({ ...item, variableId: variable?.id })
|
||||
|
||||
return (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="name">Data:</FormLabel>
|
||||
<SearchableDropdown
|
||||
items={dataItems}
|
||||
value={item.bodyPath}
|
||||
onValueChange={handleBodyPathChange}
|
||||
placeholder="Select the data"
|
||||
debounceTimeout={debounceTimeout}
|
||||
withVariableButton
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="value">Set variable:</FormLabel>
|
||||
<VariableSearchInput
|
||||
onSelectVariable={handleVariableChange}
|
||||
placeholder="Search for a variable"
|
||||
initialVariableId={item.variableId}
|
||||
debounceTimeout={debounceTimeout}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Input } from '@/components/inputs'
|
||||
import { TableListItemProps } from '@/components/TableList'
|
||||
import { VariableSearchInput } from '@/components/VariableSearchInput'
|
||||
import { Stack, FormControl, FormLabel } from '@chakra-ui/react'
|
||||
import { VariableForTest, Variable } from 'models'
|
||||
|
||||
export const VariableForTestInputs = ({
|
||||
item,
|
||||
onItemChange,
|
||||
debounceTimeout,
|
||||
}: TableListItemProps<VariableForTest>) => {
|
||||
const handleVariableSelect = (variable?: Variable) =>
|
||||
onItemChange({ ...item, variableId: variable?.id })
|
||||
const handleValueChange = (value: string) => {
|
||||
if (value === item.value) return
|
||||
onItemChange({ ...item, value })
|
||||
}
|
||||
return (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor={'name' + item.id}>Variable name:</FormLabel>
|
||||
<VariableSearchInput
|
||||
id={'name' + item.id}
|
||||
initialVariableId={item.variableId}
|
||||
onSelectVariable={handleVariableSelect}
|
||||
debounceTimeout={debounceTimeout}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor={'value' + item.id}>Test value:</FormLabel>
|
||||
<Input
|
||||
id={'value' + item.id}
|
||||
defaultValue={item.value ?? ''}
|
||||
onChange={handleValueChange}
|
||||
debounceTimeout={debounceTimeout}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Button,
|
||||
HStack,
|
||||
Spinner,
|
||||
Stack,
|
||||
Text,
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Link,
|
||||
} from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import {
|
||||
HttpMethod,
|
||||
KeyValue,
|
||||
WebhookOptions,
|
||||
VariableForTest,
|
||||
ResponseVariableMapping,
|
||||
WebhookBlock,
|
||||
defaultWebhookAttributes,
|
||||
Webhook,
|
||||
MakeComBlock,
|
||||
PabblyConnectBlock,
|
||||
} from 'models'
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import { CodeEditor } from '@/components/CodeEditor'
|
||||
import { HeadersInputs, QueryParamsInputs } from './KeyValueInputs'
|
||||
import { VariableForTestInputs } from './VariableForTestInputs'
|
||||
import { DataVariableInputs } from './ResponseMappingInputs'
|
||||
import { byId } from 'utils'
|
||||
import { ExternalLinkIcon } from '@/components/icons'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { SwitchWithLabel } from '@/components/SwitchWithLabel'
|
||||
import { TableListItemProps, TableList } from '@/components/TableList'
|
||||
import { executeWebhook } from '../../queries/executeWebhookQuery'
|
||||
import { getDeepKeys } from '../../utils/getDeepKeys'
|
||||
import { Input } from '@/components/inputs'
|
||||
import { convertVariablesForTestToVariables } from '../../utils/convertVariablesForTestToVariables'
|
||||
|
||||
type Provider = {
|
||||
name: 'Make.com' | 'Pabbly Connect'
|
||||
url: string
|
||||
}
|
||||
type Props = {
|
||||
block: WebhookBlock | MakeComBlock | PabblyConnectBlock
|
||||
onOptionsChange: (options: WebhookOptions) => void
|
||||
provider?: Provider
|
||||
}
|
||||
|
||||
export const WebhookSettings = ({
|
||||
block: { options, id: blockId, webhookId },
|
||||
onOptionsChange,
|
||||
provider,
|
||||
}: Props) => {
|
||||
const { typebot, save, webhooks, updateWebhook } = useTypebot()
|
||||
const [isTestResponseLoading, setIsTestResponseLoading] = useState(false)
|
||||
const [testResponse, setTestResponse] = useState<string>()
|
||||
const [responseKeys, setResponseKeys] = useState<string[]>([])
|
||||
|
||||
const { showToast } = useToast()
|
||||
const [localWebhook, setLocalWebhook] = useState(
|
||||
webhooks.find(byId(webhookId))
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (localWebhook) return
|
||||
const incomingWebhook = webhooks.find(byId(webhookId))
|
||||
setLocalWebhook(incomingWebhook)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [webhooks])
|
||||
|
||||
useEffect(() => {
|
||||
if (!typebot) return
|
||||
if (!localWebhook) {
|
||||
const newWebhook = {
|
||||
id: webhookId,
|
||||
...defaultWebhookAttributes,
|
||||
typebotId: typebot.id,
|
||||
} as Webhook
|
||||
updateWebhook(webhookId, newWebhook)
|
||||
}
|
||||
|
||||
return () => {
|
||||
setLocalWebhook((localWebhook) => {
|
||||
if (!localWebhook) return
|
||||
updateWebhook(webhookId, localWebhook).then()
|
||||
return localWebhook
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const handleUrlChange = (url?: string) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, url: url ?? null })
|
||||
|
||||
const handleMethodChange = (method: HttpMethod) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, method })
|
||||
|
||||
const handleQueryParamsChange = (queryParams: KeyValue[]) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, queryParams })
|
||||
|
||||
const handleHeadersChange = (headers: KeyValue[]) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, headers })
|
||||
|
||||
const handleBodyChange = (body: string) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, body })
|
||||
|
||||
const handleVariablesChange = (variablesForTest: VariableForTest[]) =>
|
||||
onOptionsChange({ ...options, variablesForTest })
|
||||
|
||||
const handleResponseMappingChange = (
|
||||
responseVariableMapping: ResponseVariableMapping[]
|
||||
) => onOptionsChange({ ...options, responseVariableMapping })
|
||||
|
||||
const handleAdvancedConfigChange = (isAdvancedConfig: boolean) =>
|
||||
onOptionsChange({ ...options, isAdvancedConfig })
|
||||
|
||||
const handleBodyFormStateChange = (isCustomBody: boolean) =>
|
||||
onOptionsChange({ ...options, isCustomBody })
|
||||
|
||||
const handleTestRequestClick = async () => {
|
||||
if (!typebot || !localWebhook) return
|
||||
setIsTestResponseLoading(true)
|
||||
await Promise.all([updateWebhook(localWebhook.id, localWebhook), save()])
|
||||
const { data, error } = await executeWebhook(
|
||||
typebot.id,
|
||||
convertVariablesForTestToVariables(
|
||||
options.variablesForTest,
|
||||
typebot.variables
|
||||
),
|
||||
{ blockId }
|
||||
)
|
||||
if (error)
|
||||
return showToast({ title: error.name, description: error.message })
|
||||
setTestResponse(JSON.stringify(data, undefined, 2))
|
||||
setResponseKeys(getDeepKeys(data))
|
||||
setIsTestResponseLoading(false)
|
||||
}
|
||||
|
||||
const ResponseMappingInputs = useMemo(
|
||||
() => (props: TableListItemProps<ResponseVariableMapping>) =>
|
||||
<DataVariableInputs {...props} dataItems={responseKeys} />,
|
||||
[responseKeys]
|
||||
)
|
||||
|
||||
if (!localWebhook) return <Spinner />
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
{provider && (
|
||||
<Alert status={'info'} bgColor={'blue.50'} rounded="md">
|
||||
<AlertIcon />
|
||||
<Stack>
|
||||
<Text>Head up to {provider.name} to configure this block:</Text>
|
||||
<Button as={Link} href={provider.url} isExternal colorScheme="blue">
|
||||
<Text mr="2">{provider.name}</Text> <ExternalLinkIcon />
|
||||
</Button>
|
||||
</Stack>
|
||||
</Alert>
|
||||
)}
|
||||
<Input
|
||||
placeholder="Paste webhook URL..."
|
||||
defaultValue={localWebhook.url ?? ''}
|
||||
onChange={handleUrlChange}
|
||||
debounceTimeout={0}
|
||||
withVariableButton={!provider}
|
||||
/>
|
||||
<SwitchWithLabel
|
||||
label="Advanced configuration"
|
||||
initialValue={options.isAdvancedConfig ?? true}
|
||||
onCheckChange={handleAdvancedConfigChange}
|
||||
/>
|
||||
{(options.isAdvancedConfig ?? true) && (
|
||||
<Stack>
|
||||
<HStack justify="space-between">
|
||||
<Text>Method:</Text>
|
||||
<DropdownList<HttpMethod>
|
||||
currentItem={localWebhook.method as HttpMethod}
|
||||
onItemSelect={handleMethodChange}
|
||||
items={Object.values(HttpMethod)}
|
||||
/>
|
||||
</HStack>
|
||||
<Accordion allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Query params
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={localWebhook.queryParams}
|
||||
onItemsChange={handleQueryParamsChange}
|
||||
Item={QueryParamsInputs}
|
||||
addLabel="Add a param"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Headers
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={localWebhook.headers}
|
||||
onItemsChange={handleHeadersChange}
|
||||
Item={HeadersInputs}
|
||||
addLabel="Add a value"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Body
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<SwitchWithLabel
|
||||
label="Custom body"
|
||||
initialValue={options.isCustomBody ?? true}
|
||||
onCheckChange={handleBodyFormStateChange}
|
||||
/>
|
||||
{(options.isCustomBody ?? true) && (
|
||||
<CodeEditor
|
||||
value={localWebhook.body ?? ''}
|
||||
lang="json"
|
||||
onChange={handleBodyChange}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Variable values for test
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<TableList<VariableForTest>
|
||||
initialItems={
|
||||
options?.variablesForTest ?? { byId: {}, allIds: [] }
|
||||
}
|
||||
onItemsChange={handleVariablesChange}
|
||||
Item={VariableForTestInputs}
|
||||
addLabel="Add an entry"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)}
|
||||
<Stack>
|
||||
{localWebhook.url && (
|
||||
<Button
|
||||
onClick={handleTestRequestClick}
|
||||
colorScheme="blue"
|
||||
isLoading={isTestResponseLoading}
|
||||
>
|
||||
Test the request
|
||||
</Button>
|
||||
)}
|
||||
{testResponse && (
|
||||
<CodeEditor isReadOnly lang="json" value={testResponse} />
|
||||
)}
|
||||
{(testResponse || options?.responseVariableMapping.length > 0) && (
|
||||
<Accordion allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Save in variables
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<TableList<ResponseVariableMapping>
|
||||
initialItems={options.responseVariableMapping}
|
||||
onItemsChange={handleResponseMappingChange}
|
||||
Item={ResponseMappingInputs}
|
||||
addLabel="Add an entry"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { WebhookSettings } from './WebhookSettings'
|
||||
@@ -0,0 +1,4 @@
|
||||
export { duplicateWebhookQueries } from './queries/duplicateWebhookQuery'
|
||||
export { WebhookSettings } from './components/WebhookSettings'
|
||||
export { WebhookContent } from './components/WebhookContent'
|
||||
export { WebhookIcon } from './components/WebhookIcon'
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Webhook } from 'models'
|
||||
import { sendRequest } from 'utils'
|
||||
import { saveWebhookQuery } from './saveWebhookQuery'
|
||||
|
||||
export const duplicateWebhookQueries = async (
|
||||
typebotId: string,
|
||||
existingWebhookId: string,
|
||||
newWebhookId: string
|
||||
): Promise<Webhook | undefined> => {
|
||||
const { data } = await sendRequest<{ webhook: Webhook }>(
|
||||
`/api/webhooks/${existingWebhookId}`
|
||||
)
|
||||
if (!data) return
|
||||
const newWebhook = { ...data.webhook, id: newWebhookId, typebotId }
|
||||
await saveWebhookQuery(newWebhook.id, newWebhook)
|
||||
return newWebhook
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Variable, WebhookResponse } from 'models'
|
||||
import { getViewerUrl, sendRequest } from 'utils'
|
||||
|
||||
export const executeWebhook = (
|
||||
typebotId: string,
|
||||
variables: Variable[],
|
||||
{ blockId }: { blockId: string }
|
||||
) =>
|
||||
sendRequest<WebhookResponse>({
|
||||
url: `${getViewerUrl({
|
||||
isBuilder: true,
|
||||
})}/api/typebots/${typebotId}/blocks/${blockId}/executeWebhook`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
variables,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Webhook } from 'models'
|
||||
import { sendRequest } from 'utils'
|
||||
|
||||
export const saveWebhookQuery = (
|
||||
webhookId: string,
|
||||
webhook: Partial<Webhook>
|
||||
) =>
|
||||
sendRequest<{ webhook: Webhook }>({
|
||||
method: 'PUT',
|
||||
url: `/api/webhooks/${webhookId}`,
|
||||
body: webhook,
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Variable, VariableForTest } from 'models'
|
||||
|
||||
export const convertVariablesForTestToVariables = (
|
||||
variablesForTest: VariableForTest[],
|
||||
variables: Variable[]
|
||||
): Variable[] => {
|
||||
if (!variablesForTest) return []
|
||||
return [
|
||||
...variables,
|
||||
...variablesForTest
|
||||
.filter((v) => v.variableId)
|
||||
.map((variableForTest) => {
|
||||
const variable = variables.find(
|
||||
(v) => v.id === variableForTest.variableId
|
||||
) as Variable
|
||||
return { ...variable, value: variableForTest.value }
|
||||
}, {}),
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const getDeepKeys = (obj: any): string[] => {
|
||||
let keys: string[] = []
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
||||
const subkeys = getDeepKeys(obj[key])
|
||||
keys = keys.concat(
|
||||
subkeys.map(function (subkey) {
|
||||
return key + '.' + subkey
|
||||
})
|
||||
)
|
||||
} else if (Array.isArray(obj[key])) {
|
||||
for (let i = 0; i < obj[key].length; i++) {
|
||||
const subkeys = getDeepKeys(obj[key][i])
|
||||
keys = keys.concat(
|
||||
subkeys.map(function (subkey) {
|
||||
return key + '[' + i + ']' + '.' + subkey
|
||||
})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
keys.push(key)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import test, { expect, Page } from '@playwright/test'
|
||||
import {
|
||||
createWebhook,
|
||||
importTypebotInDatabase,
|
||||
} from 'utils/playwright/databaseActions'
|
||||
import { HttpMethod } from 'models'
|
||||
import cuid from 'cuid'
|
||||
import { getTestAsset } from '@/test/utils/playwright'
|
||||
|
||||
test.describe('Webhook block', () => {
|
||||
test('easy configuration should work', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await importTypebotInDatabase(
|
||||
getTestAsset('typebots/integrations/easyConfigWebhook.json'),
|
||||
{
|
||||
id: typebotId,
|
||||
}
|
||||
)
|
||||
await createWebhook(typebotId, { method: HttpMethod.POST })
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await page.click('text=Configure...')
|
||||
await page.fill(
|
||||
'input[placeholder="Paste webhook URL..."]',
|
||||
`${process.env.NEXTAUTH_URL}/api/mock/webhook-easy-config`
|
||||
)
|
||||
await page.click('text=Test the request')
|
||||
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
|
||||
`"Group #1": "answer value", "Group #2": "20", "Group #2 (1)": "Yes"`
|
||||
)
|
||||
})
|
||||
|
||||
test('its configuration should work', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await importTypebotInDatabase(
|
||||
getTestAsset('typebots/integrations/webhook.json'),
|
||||
{
|
||||
id: typebotId,
|
||||
}
|
||||
)
|
||||
await createWebhook(typebotId)
|
||||
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await page.click('text=Configure...')
|
||||
await page.fill(
|
||||
'input[placeholder="Paste webhook URL..."]',
|
||||
`${process.env.NEXTAUTH_URL}/api/mock/webhook`
|
||||
)
|
||||
await page.click('text=Advanced configuration')
|
||||
await page.click('text=GET')
|
||||
await page.click('text=POST')
|
||||
|
||||
await page.click('text=Query params')
|
||||
await page.click('text=Add a param')
|
||||
await page.fill('input[placeholder="e.g. email"]', 'firstParam')
|
||||
await page.fill('input[placeholder="e.g. {{Email}}"]', '{{secret 1}}')
|
||||
|
||||
await page.click('text=Add a param')
|
||||
await page.fill('input[placeholder="e.g. email"] >> nth=1', 'secondParam')
|
||||
await page.fill(
|
||||
'input[placeholder="e.g. {{Email}}"] >> nth=1',
|
||||
'{{secret 2}}'
|
||||
)
|
||||
|
||||
await page.click('text=Headers')
|
||||
await page.waitForTimeout(200)
|
||||
await page.getByRole('button', { name: 'Add a value' }).click()
|
||||
await page.fill('input[placeholder="e.g. Content-Type"]', 'Custom-Typebot')
|
||||
await page.fill(
|
||||
'input[placeholder="e.g. application/json"]',
|
||||
'{{secret 3}}'
|
||||
)
|
||||
|
||||
await page.click('text=Body')
|
||||
await page.click('text=Custom body')
|
||||
await page.fill('div[role="textbox"]', '{ "customField": "{{secret 4}}" }')
|
||||
|
||||
await page.click('text=Variable values for test')
|
||||
await addTestVariable(page, 'secret 1', 'secret1')
|
||||
await addTestVariable(page, 'secret 2', 'secret2')
|
||||
await addTestVariable(page, 'secret 3', 'secret3')
|
||||
await addTestVariable(page, 'secret 4', 'secret4')
|
||||
|
||||
await page.click('text=Test the request')
|
||||
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
|
||||
'"statusCode": 200'
|
||||
)
|
||||
|
||||
await page.click('text=Save in variables')
|
||||
await page.click('text=Add an entry >> nth=-1')
|
||||
await page.click('input[placeholder="Select the data"]')
|
||||
await page.click('text=data[0].name')
|
||||
})
|
||||
})
|
||||
|
||||
const addTestVariable = async (page: Page, name: string, value: string) => {
|
||||
await page.click('text=Add an entry')
|
||||
await page.click('[data-testid="variables-input"] >> nth=-1')
|
||||
await page.click(`text="${name}"`)
|
||||
await page.fill('input >> nth=-1', value)
|
||||
}
|
||||
Reference in New Issue
Block a user