(api) Add CRUD typebot endpoints

Closes #320, closes #696
This commit is contained in:
Baptiste Arnaud
2023-08-17 09:39:11 +02:00
parent 019f72ac7e
commit 454d320c6b
78 changed files with 25014 additions and 1073 deletions

View File

@@ -1,7 +1,11 @@
import { TextInput } from '@/components/inputs'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { FormLabel, Stack } from '@chakra-ui/react'
import { EmailInputOptions, Variable } from '@typebot.io/schemas'
import {
EmailInputOptions,
Variable,
invalidEmailDefaultRetryMessage,
} from '@typebot.io/schemas'
import React from 'react'
type Props = {
@@ -33,7 +37,9 @@ export const EmailInputSettings = ({ options, onOptionsChange }: Props) => {
/>
<TextInput
label="Retry message:"
defaultValue={options.retryMessageContent}
defaultValue={
options.retryMessageContent ?? invalidEmailDefaultRetryMessage
}
onChange={handleRetryMessageChange}
/>
<Stack>

View File

@@ -1,7 +1,11 @@
import test, { expect } from '@playwright/test'
import { createTypebots } from '@typebot.io/lib/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from '@typebot.io/lib/playwright/databaseHelpers'
import { defaultEmailInputOptions, InputBlockType } from '@typebot.io/schemas'
import {
defaultEmailInputOptions,
InputBlockType,
invalidEmailDefaultRetryMessage,
} from '@typebot.io/schemas'
import { createId } from '@paralleldrive/cuid2'
test.describe('Email input block', () => {
@@ -35,7 +39,7 @@ test.describe('Email input block', () => {
await expect(page.locator('text=Your email...')).toBeVisible()
await page.getByLabel('Button label:').fill('Go')
await page.fill(
`input[value="${defaultEmailInputOptions.retryMessageContent}"]`,
`input[value="${invalidEmailDefaultRetryMessage}"]`,
'Try again bro'
)

View File

@@ -1,15 +1,13 @@
import { Text } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { MakeComBlock } from '@typebot.io/schemas'
import { byId, isNotDefined } from '@typebot.io/lib'
import { isNotDefined } from '@typebot.io/lib'
type Props = {
block: MakeComBlock
}
export const MakeComContent = ({ block }: Props) => {
const { webhooks } = useTypebot()
const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
const webhook = block.options.webhook
if (isNotDefined(webhook?.body))
return <Text color="gray.500">Configure...</Text>

View File

@@ -1,9 +1,7 @@
import { Alert, AlertIcon, Button, Link, Stack, Text } from '@chakra-ui/react'
import { ExternalLinkIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { MakeComBlock, Webhook, WebhookOptions } from '@typebot.io/schemas'
import React, { useCallback, useEffect, useState } from 'react'
import { byId } from '@typebot.io/lib'
import React from 'react'
import { WebhookAdvancedConfigForm } from '../../webhook/components/WebhookAdvancedConfigForm'
type Props = {
@@ -12,45 +10,18 @@ type Props = {
}
export const MakeComSettings = ({
block: { webhookId, id: blockId, options },
block: { id: blockId, options },
onOptionsChange,
}: Props) => {
const { webhooks, updateWebhook } = useTypebot()
const webhook = webhooks.find(byId(webhookId))
const [localWebhook, _setLocalWebhook] = useState(webhook)
const setLocalWebhook = useCallback(
async (newLocalWebhook: Webhook) => {
if (options.webhook) {
onOptionsChange({
...options,
webhook: newLocalWebhook,
})
return
}
_setLocalWebhook(newLocalWebhook)
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
},
[onOptionsChange, options, updateWebhook]
)
useEffect(() => {
if (
!localWebhook ||
localWebhook.url ||
!webhook?.url ||
webhook.url === localWebhook.url ||
options.webhook
)
return
setLocalWebhook({
...localWebhook,
url: webhook?.url,
const setLocalWebhook = async (newLocalWebhook: Webhook) => {
if (!options.webhook) return
onOptionsChange({
...options,
webhook: newLocalWebhook,
})
}, [webhook, localWebhook, setLocalWebhook, options.webhook])
}
const url = options.webhook?.url ?? localWebhook?.url
const url = options.webhook?.url
return (
<Stack spacing={4}>
@@ -72,10 +43,10 @@ export const MakeComSettings = ({
</Stack>
)}
</Alert>
{(localWebhook || options.webhook) && (
{options.webhook && (
<WebhookAdvancedConfigForm
blockId={blockId}
webhook={(options.webhook ?? localWebhook) as Webhook}
webhook={options.webhook as Webhook}
options={options}
onWebhookChange={setLocalWebhook}
onOptionsChange={onOptionsChange}

View File

@@ -1,15 +1,13 @@
import { Text } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { PabblyConnectBlock } from '@typebot.io/schemas'
import { byId, isNotDefined } from '@typebot.io/lib'
import { isNotDefined } from '@typebot.io/lib'
type Props = {
block: PabblyConnectBlock
}
export const PabblyConnectContent = ({ block }: Props) => {
const { webhooks } = useTypebot()
const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
const webhook = block.options.webhook
if (isNotDefined(webhook?.body))
return <Text color="gray.500">Configure...</Text>

View File

@@ -1,13 +1,11 @@
import { Alert, AlertIcon, Button, Link, Stack, Text } from '@chakra-ui/react'
import { ExternalLinkIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import {
PabblyConnectBlock,
Webhook,
WebhookOptions,
} from '@typebot.io/schemas'
import React, { useState } from 'react'
import { byId } from '@typebot.io/lib'
import React from 'react'
import { WebhookAdvancedConfigForm } from '../../webhook/components/WebhookAdvancedConfigForm'
import { TextInput } from '@/components/inputs'
@@ -17,35 +15,23 @@ type Props = {
}
export const PabblyConnectSettings = ({
block: { webhookId, id: blockId, options },
block: { id: blockId, options },
onOptionsChange,
}: Props) => {
const { webhooks, updateWebhook } = useTypebot()
const [localWebhook, _setLocalWebhook] = useState(
webhooks.find(byId(webhookId))
)
const setLocalWebhook = async (newLocalWebhook: Webhook) => {
if (options.webhook) {
onOptionsChange({
...options,
webhook: newLocalWebhook,
})
return
}
_setLocalWebhook(newLocalWebhook)
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
if (!options.webhook) return
onOptionsChange({
...options,
webhook: newLocalWebhook,
})
}
const handleUrlChange = (url: string) =>
localWebhook &&
setLocalWebhook({
...localWebhook,
url,
})
const updateUrl = (url: string) => {
if (!options.webhook) return
onOptionsChange({ ...options, webhook: { ...options.webhook, url } })
}
const url = options.webhook?.url ?? localWebhook?.url
const url = options.webhook?.url
return (
<Stack spacing={4}>
@@ -70,14 +56,14 @@ export const PabblyConnectSettings = ({
<TextInput
placeholder="Paste webhook URL..."
defaultValue={url ?? ''}
onChange={handleUrlChange}
onChange={updateUrl}
withVariableButton={false}
debounceTimeout={0}
/>
{(localWebhook || options.webhook) && (
{options.webhook && (
<WebhookAdvancedConfigForm
blockId={blockId}
webhook={(options.webhook ?? localWebhook) as Webhook}
webhook={options.webhook as Webhook}
options={options}
onWebhookChange={setLocalWebhook}
onOptionsChange={onOptionsChange}

View File

@@ -47,7 +47,7 @@ export const WebhookAdvancedConfigForm = ({
onWebhookChange,
onOptionsChange,
}: Props) => {
const { typebot, save, updateWebhook } = useTypebot()
const { typebot, save } = useTypebot()
const [isTestResponseLoading, setIsTestResponseLoading] = useState(false)
const [testResponse, setTestResponse] = useState<string>()
const [responseKeys, setResponseKeys] = useState<string[]>([])
@@ -80,8 +80,7 @@ export const WebhookAdvancedConfigForm = ({
const executeTestRequest = async () => {
if (!typebot) return
setIsTestResponseLoading(true)
if (!options.webhook)
await Promise.all([updateWebhook(webhook.id, webhook), save()])
if (!options.webhook) await save()
else await save()
const { data, error } = await executeWebhook(
typebot.id,

View File

@@ -1,17 +1,15 @@
import { Stack, Text } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { WebhookBlock } from '@typebot.io/schemas'
import { byId } from '@typebot.io/lib'
import { SetVariableLabel } from '@/components/SetVariableLabel'
type Props = {
block: WebhookBlock
}
export const WebhookContent = ({ block: { options, webhookId } }: Props) => {
export const WebhookContent = ({ block: { options } }: Props) => {
const { typebot } = useTypebot()
const { webhooks } = useTypebot()
const webhook = options.webhook ?? webhooks.find(byId(webhookId))
const webhook = options.webhook
if (!webhook?.url) return <Text color="gray.500">Configure...</Text>
return (

View File

@@ -1,8 +1,6 @@
import React, { useState } from 'react'
import React from 'react'
import { Spinner, Stack } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { WebhookOptions, Webhook, WebhookBlock } from '@typebot.io/schemas'
import { byId } from '@typebot.io/lib'
import { TextInput } from '@/components/inputs'
import { WebhookAdvancedConfigForm } from './WebhookAdvancedConfigForm'
@@ -12,42 +10,32 @@ type Props = {
}
export const WebhookSettings = ({
block: { webhookId, id: blockId, options },
block: { id: blockId, options },
onOptionsChange,
}: Props) => {
const { webhooks, updateWebhook } = useTypebot()
const [localWebhook, _setLocalWebhook] = useState(
webhooks.find(byId(webhookId))
)
const setLocalWebhook = async (newLocalWebhook: Webhook) => {
if (options.webhook) {
onOptionsChange({ ...options, webhook: newLocalWebhook })
return
}
_setLocalWebhook(newLocalWebhook)
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
if (!options.webhook) return
onOptionsChange({ ...options, webhook: newLocalWebhook })
return
}
const updateUrl = (url: string) => {
if (options.webhook)
onOptionsChange({ ...options, webhook: { ...options.webhook, url } })
else if (localWebhook)
setLocalWebhook({ ...localWebhook, url: url ?? undefined })
if (!options.webhook) return
onOptionsChange({ ...options, webhook: { ...options.webhook, url } })
}
if (!localWebhook && !options.webhook) return <Spinner />
if (!options.webhook) return <Spinner />
return (
<Stack spacing={4}>
<TextInput
placeholder="Paste webhook URL..."
defaultValue={options.webhook?.url ?? localWebhook?.url ?? ''}
defaultValue={options.webhook?.url ?? ''}
onChange={updateUrl}
/>
<WebhookAdvancedConfigForm
blockId={blockId}
webhook={(options.webhook ?? localWebhook) as Webhook}
webhook={options.webhook as Webhook}
options={options}
onWebhookChange={setLocalWebhook}
onOptionsChange={onOptionsChange}

View File

@@ -174,7 +174,7 @@ test.describe('API', () => {
expect(data.resultExample).toMatchObject({
message: 'This is a sample result, it has been generated ⬇️',
Welcome: 'Hi!',
Email: 'test@email.com',
Email: 'user@email.com',
Name: 'answer value',
Services: 'Website dev, Content Marketing, Social Media, UI / UX Design',
'Additional information': 'answer value',

View File

@@ -1,15 +1,13 @@
import { Text } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { ZapierBlock } from '@typebot.io/schemas'
import { byId, isNotDefined } from '@typebot.io/lib'
import { isNotDefined } from '@typebot.io/lib'
type Props = {
block: ZapierBlock
}
export const ZapierContent = ({ block }: Props) => {
const { webhooks } = useTypebot()
const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
const webhook = block.options.webhook
if (isNotDefined(webhook?.body))
return <Text color="gray.500">Configure...</Text>

View File

@@ -1,9 +1,7 @@
import { Alert, AlertIcon, Button, Link, Stack, Text } from '@chakra-ui/react'
import { ExternalLinkIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { Webhook, WebhookOptions, ZapierBlock } from '@typebot.io/schemas'
import React, { useCallback, useEffect, useState } from 'react'
import { byId } from '@typebot.io/lib'
import React from 'react'
import { WebhookAdvancedConfigForm } from '../../webhook/components/WebhookAdvancedConfigForm'
type Props = {
@@ -12,45 +10,19 @@ type Props = {
}
export const ZapierSettings = ({
block: { webhookId, id: blockId, options },
block: { id: blockId, options },
onOptionsChange,
}: Props) => {
const { webhooks, updateWebhook } = useTypebot()
const webhook = webhooks.find(byId(webhookId))
const [localWebhook, _setLocalWebhook] = useState(webhook)
const setLocalWebhook = useCallback(
async (newLocalWebhook: Webhook) => {
if (options.webhook) {
onOptionsChange({
...options,
webhook: newLocalWebhook,
})
return
}
_setLocalWebhook(newLocalWebhook)
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
},
[onOptionsChange, options, updateWebhook]
)
useEffect(() => {
if (
!localWebhook ||
localWebhook.url ||
!webhook?.url ||
webhook.url === localWebhook.url ||
options.webhook
)
return
setLocalWebhook({
...localWebhook,
url: webhook?.url,
const setLocalWebhook = async (newLocalWebhook: Webhook) => {
if (!options.webhook) return
onOptionsChange({
...options,
webhook: newLocalWebhook,
})
}, [webhook, localWebhook, setLocalWebhook, options.webhook])
return
}
const url = options.webhook?.url ?? localWebhook?.url
const url = options.webhook?.url
return (
<Stack spacing={4}>
@@ -72,10 +44,10 @@ export const ZapierSettings = ({
</Stack>
)}
</Alert>
{(localWebhook || options.webhook) && (
{options.webhook && (
<WebhookAdvancedConfigForm
blockId={blockId}
webhook={(options.webhook ?? localWebhook) as Webhook}
webhook={options.webhook as Webhook}
options={options}
onWebhookChange={setLocalWebhook}
onOptionsChange={onOptionsChange}

View File

@@ -33,7 +33,7 @@ const Expression = ({
case 'Custom':
case undefined:
return (
<Text>
<Text as="span">
{variableName} = {options.expressionToEvaluate}
</Text>
)
@@ -48,7 +48,7 @@ const Expression = ({
byId(options.mapListItemParams?.targetListVariableId)
)
return (
<Text>
<Text as="span">
{variableName} = item in ${targetListVariable?.name} with same index
as ${baseItemVariable?.name} in ${baseListVariable?.name}
</Text>
@@ -64,7 +64,7 @@ const Expression = ({
case 'Moment of the day':
case 'Yesterday': {
return (
<Text>
<Text as="span">
{variableName} = <Tag colorScheme="purple">System.{options.type}</Tag>
</Text>
)