@@ -9,7 +9,7 @@ type Props = {
|
||||
|
||||
export const MakeComContent = ({ block }: Props) => {
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(block.webhookId))
|
||||
const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
|
||||
@@ -22,10 +22,17 @@ export const MakeComSettings = ({
|
||||
|
||||
const setLocalWebhook = useCallback(
|
||||
async (newLocalWebhook: Webhook) => {
|
||||
if (options.webhook) {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
webhook: newLocalWebhook,
|
||||
})
|
||||
return
|
||||
}
|
||||
_setLocalWebhook(newLocalWebhook)
|
||||
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
|
||||
},
|
||||
[updateWebhook]
|
||||
[onOptionsChange, options, updateWebhook]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -33,20 +40,23 @@ export const MakeComSettings = ({
|
||||
!localWebhook ||
|
||||
localWebhook.url ||
|
||||
!webhook?.url ||
|
||||
webhook.url === localWebhook.url
|
||||
webhook.url === localWebhook.url ||
|
||||
options.webhook
|
||||
)
|
||||
return
|
||||
setLocalWebhook({
|
||||
...localWebhook,
|
||||
url: webhook?.url,
|
||||
})
|
||||
}, [webhook, localWebhook, setLocalWebhook])
|
||||
}, [webhook, localWebhook, setLocalWebhook, options.webhook])
|
||||
|
||||
const url = options.webhook?.url ?? localWebhook?.url
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Alert status={localWebhook?.url ? 'success' : 'info'} rounded="md">
|
||||
<Alert status={url ? 'success' : 'info'} rounded="md">
|
||||
<AlertIcon />
|
||||
{localWebhook?.url ? (
|
||||
{url ? (
|
||||
<>Your scenario is correctly configured 🚀</>
|
||||
) : (
|
||||
<Stack>
|
||||
@@ -62,10 +72,10 @@ export const MakeComSettings = ({
|
||||
</Stack>
|
||||
)}
|
||||
</Alert>
|
||||
{localWebhook && (
|
||||
{(localWebhook || options.webhook) && (
|
||||
<WebhookAdvancedConfigForm
|
||||
blockId={blockId}
|
||||
webhook={localWebhook}
|
||||
webhook={(options.webhook ?? localWebhook) as Webhook}
|
||||
options={options}
|
||||
onWebhookChange={setLocalWebhook}
|
||||
onOptionsChange={onOptionsChange}
|
||||
|
||||
@@ -9,7 +9,7 @@ type Props = {
|
||||
|
||||
export const PabblyConnectContent = ({ block }: Props) => {
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(block.webhookId))
|
||||
const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
|
||||
@@ -27,6 +27,13 @@ export const PabblyConnectSettings = ({
|
||||
)
|
||||
|
||||
const setLocalWebhook = async (newLocalWebhook: Webhook) => {
|
||||
if (options.webhook) {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
webhook: newLocalWebhook,
|
||||
})
|
||||
return
|
||||
}
|
||||
_setLocalWebhook(newLocalWebhook)
|
||||
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
|
||||
}
|
||||
@@ -38,11 +45,13 @@ export const PabblyConnectSettings = ({
|
||||
url,
|
||||
})
|
||||
|
||||
const url = options.webhook?.url ?? localWebhook?.url
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Alert status={localWebhook?.url ? 'success' : 'info'} rounded="md">
|
||||
<Alert status={url ? 'success' : 'info'} rounded="md">
|
||||
<AlertIcon />
|
||||
{localWebhook?.url ? (
|
||||
{url ? (
|
||||
<>Your scenario is correctly configured 🚀</>
|
||||
) : (
|
||||
<Stack>
|
||||
@@ -60,15 +69,15 @@ export const PabblyConnectSettings = ({
|
||||
</Alert>
|
||||
<TextInput
|
||||
placeholder="Paste webhook URL..."
|
||||
defaultValue={localWebhook?.url ?? ''}
|
||||
defaultValue={url ?? ''}
|
||||
onChange={handleUrlChange}
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
{localWebhook && (
|
||||
{(localWebhook || options.webhook) && (
|
||||
<WebhookAdvancedConfigForm
|
||||
blockId={blockId}
|
||||
webhook={localWebhook}
|
||||
webhook={(options.webhook ?? localWebhook) as Webhook}
|
||||
options={options}
|
||||
onWebhookChange={setLocalWebhook}
|
||||
onOptionsChange={onOptionsChange}
|
||||
|
||||
@@ -2,7 +2,7 @@ import prisma from '@/lib/prisma'
|
||||
import { canReadTypebots } from '@/helpers/databaseRules'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Typebot, Webhook } from '@typebot.io/schemas'
|
||||
import { Typebot } from '@typebot.io/schemas'
|
||||
import { z } from 'zod'
|
||||
import { fetchLinkedTypebots } from '@/features/blocks/logic/typebotLink/helpers/fetchLinkedTypebots'
|
||||
import { parseResultExample } from '../helpers/parseResultExample'
|
||||
@@ -45,20 +45,15 @@ export const getResultExample = authenticatedProcedure
|
||||
groups: true,
|
||||
edges: true,
|
||||
variables: true,
|
||||
webhooks: true,
|
||||
},
|
||||
})) as
|
||||
| (Pick<Typebot, 'groups' | 'edges' | 'variables'> & {
|
||||
webhooks: Webhook[]
|
||||
})
|
||||
| null
|
||||
})) as Pick<Typebot, 'groups' | 'edges' | 'variables'> | null
|
||||
|
||||
if (!typebot)
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
||||
|
||||
const block = typebot.groups
|
||||
.flatMap((g) => g.blocks)
|
||||
.find((s) => s.id === blockId)
|
||||
.flatMap((group) => group.blocks)
|
||||
.find((block) => block.id === blockId)
|
||||
|
||||
if (!block)
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Block not found' })
|
||||
|
||||
@@ -2,9 +2,10 @@ import prisma from '@/lib/prisma'
|
||||
import { canReadTypebots } from '@/helpers/databaseRules'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Group, Typebot, Webhook, WebhookBlock } from '@typebot.io/schemas'
|
||||
import { Group, IntegrationBlockType, Typebot } from '@typebot.io/schemas'
|
||||
import { byId, isWebhookBlock, parseGroupTitle } from '@typebot.io/lib'
|
||||
import { z } from 'zod'
|
||||
import { Webhook } from '@typebot.io/prisma'
|
||||
|
||||
export const listWebhookBlocks = authenticatedProcedure
|
||||
.meta({
|
||||
@@ -28,6 +29,12 @@ export const listWebhookBlocks = authenticatedProcedure
|
||||
webhookBlocks: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
type: z.enum([
|
||||
IntegrationBlockType.WEBHOOK,
|
||||
IntegrationBlockType.ZAPIER,
|
||||
IntegrationBlockType.MAKE_COM,
|
||||
IntegrationBlockType.PABBLY_CONNECT,
|
||||
]),
|
||||
label: z.string(),
|
||||
url: z.string().optional(),
|
||||
})
|
||||
@@ -46,17 +53,27 @@ export const listWebhookBlocks = authenticatedProcedure
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
||||
|
||||
const webhookBlocks = (typebot?.groups as Group[]).reduce<
|
||||
{ id: string; label: string; url: string | undefined }[]
|
||||
{
|
||||
id: string
|
||||
label: string
|
||||
url: string | undefined
|
||||
type:
|
||||
| IntegrationBlockType.WEBHOOK
|
||||
| IntegrationBlockType.ZAPIER
|
||||
| IntegrationBlockType.MAKE_COM
|
||||
| IntegrationBlockType.PABBLY_CONNECT
|
||||
}[]
|
||||
>((webhookBlocks, group) => {
|
||||
const blocks = group.blocks.filter((block) =>
|
||||
isWebhookBlock(block)
|
||||
) as WebhookBlock[]
|
||||
const blocks = group.blocks.filter(isWebhookBlock)
|
||||
return [
|
||||
...webhookBlocks,
|
||||
...blocks.map((b) => ({
|
||||
id: b.id,
|
||||
label: `${parseGroupTitle(group.title)} > ${b.id}`,
|
||||
url: typebot?.webhooks.find(byId(b.webhookId))?.url ?? undefined,
|
||||
...blocks.map((block) => ({
|
||||
id: block.id,
|
||||
type: block.type,
|
||||
label: `${parseGroupTitle(group.title)} > ${block.id}`,
|
||||
url: block.options.webhook
|
||||
? block.options.webhook.url
|
||||
: typebot?.webhooks.find(byId(block.webhookId))?.url ?? undefined,
|
||||
})),
|
||||
]
|
||||
}, [])
|
||||
|
||||
@@ -2,9 +2,10 @@ import prisma from '@/lib/prisma'
|
||||
import { canWriteTypebots } from '@/helpers/databaseRules'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Typebot, Webhook, WebhookBlock } from '@typebot.io/schemas'
|
||||
import { Typebot, WebhookBlock } from '@typebot.io/schemas'
|
||||
import { byId, isWebhookBlock } from '@typebot.io/lib'
|
||||
import { z } from 'zod'
|
||||
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
|
||||
|
||||
export const subscribeWebhook = authenticatedProcedure
|
||||
.meta({
|
||||
@@ -34,9 +35,8 @@ export const subscribeWebhook = authenticatedProcedure
|
||||
where: canWriteTypebots(typebotId, user),
|
||||
select: {
|
||||
groups: true,
|
||||
webhooks: true,
|
||||
},
|
||||
})) as (Pick<Typebot, 'groups'> & { webhooks: Webhook[] }) | null
|
||||
})) as Pick<Typebot, 'groups'> | null
|
||||
|
||||
if (!typebot)
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
||||
@@ -51,18 +51,50 @@ export const subscribeWebhook = authenticatedProcedure
|
||||
message: 'Webhook block not found',
|
||||
})
|
||||
|
||||
await prisma.webhook.upsert({
|
||||
where: { id: webhookBlock.webhookId },
|
||||
update: { url, body: '{{state}}', method: 'POST' },
|
||||
create: {
|
||||
url,
|
||||
body: '{{state}}',
|
||||
method: 'POST',
|
||||
typebotId,
|
||||
headers: [],
|
||||
queryParams: [],
|
||||
},
|
||||
})
|
||||
const newWebhook = {
|
||||
id: webhookBlock.webhookId ?? webhookBlock.id,
|
||||
url,
|
||||
body: '{{state}}',
|
||||
method: HttpMethod.POST,
|
||||
headers: [],
|
||||
queryParams: [],
|
||||
}
|
||||
|
||||
if (webhookBlock.webhookId)
|
||||
await prisma.webhook.upsert({
|
||||
where: { id: webhookBlock.webhookId },
|
||||
update: { url, body: newWebhook.body, method: newWebhook.method },
|
||||
create: {
|
||||
typebotId,
|
||||
...newWebhook,
|
||||
},
|
||||
})
|
||||
else {
|
||||
const updatedGroups = typebot.groups.map((group) =>
|
||||
group.id !== webhookBlock.groupId
|
||||
? group
|
||||
: {
|
||||
...group,
|
||||
blocks: group.blocks.map((block) =>
|
||||
block.id !== webhookBlock.id
|
||||
? block
|
||||
: {
|
||||
...block,
|
||||
options: {
|
||||
...webhookBlock.options,
|
||||
webhook: newWebhook,
|
||||
},
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
await prisma.typebot.updateMany({
|
||||
where: { id: typebotId },
|
||||
data: {
|
||||
groups: updatedGroups,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
id: blockId,
|
||||
|
||||
@@ -50,10 +50,45 @@ export const unsubscribeWebhook = authenticatedProcedure
|
||||
message: 'Webhook block not found',
|
||||
})
|
||||
|
||||
await prisma.webhook.update({
|
||||
where: { id: webhookBlock.webhookId },
|
||||
data: { url: null },
|
||||
})
|
||||
if (webhookBlock.webhookId)
|
||||
await prisma.webhook.update({
|
||||
where: { id: webhookBlock.webhookId },
|
||||
data: { url: null },
|
||||
})
|
||||
else {
|
||||
if (!webhookBlock.options.webhook)
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Webhook block not found',
|
||||
})
|
||||
const updatedGroups = typebot.groups.map((group) =>
|
||||
group.id !== webhookBlock.groupId
|
||||
? group
|
||||
: {
|
||||
...group,
|
||||
blocks: group.blocks.map((block) =>
|
||||
block.id !== webhookBlock.id
|
||||
? block
|
||||
: {
|
||||
...block,
|
||||
options: {
|
||||
...webhookBlock.options,
|
||||
webhook: {
|
||||
...webhookBlock.options.webhook,
|
||||
url: undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
await prisma.typebot.updateMany({
|
||||
where: { id: typebotId },
|
||||
data: {
|
||||
groups: updatedGroups,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
id: blockId,
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
Text,
|
||||
} from '@chakra-ui/react'
|
||||
import {
|
||||
HttpMethod,
|
||||
KeyValue,
|
||||
VariableForTest,
|
||||
ResponseVariableMapping,
|
||||
@@ -31,6 +30,7 @@ import { QueryParamsInputs, HeadersInputs } from './KeyValueInputs'
|
||||
import { DataVariableInputs } from './ResponseMappingInputs'
|
||||
import { VariableForTestInputs } from './VariableForTestInputs'
|
||||
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
|
||||
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
|
||||
|
||||
type Props = {
|
||||
blockId: string
|
||||
@@ -78,9 +78,11 @@ export const WebhookAdvancedConfigForm = ({
|
||||
onOptionsChange({ ...options, isCustomBody })
|
||||
|
||||
const executeTestRequest = async () => {
|
||||
if (!typebot || !webhook) return
|
||||
if (!typebot) return
|
||||
setIsTestResponseLoading(true)
|
||||
await Promise.all([updateWebhook(webhook.id, webhook), save()])
|
||||
if (!options.webhook)
|
||||
await Promise.all([updateWebhook(webhook.id, webhook), save()])
|
||||
else await save()
|
||||
const { data, error } = await executeWebhook(
|
||||
typebot.id,
|
||||
convertVariablesForTestToVariables(
|
||||
|
||||
@@ -8,10 +8,10 @@ type Props = {
|
||||
block: WebhookBlock
|
||||
}
|
||||
|
||||
export const WebhookContent = ({ block: { webhookId, options } }: Props) => {
|
||||
export const WebhookContent = ({ block: { options, webhookId } }: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(webhookId))
|
||||
const webhook = options.webhook ?? webhooks.find(byId(webhookId))
|
||||
|
||||
if (!webhook?.url) return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
|
||||
@@ -21,25 +21,33 @@ export const WebhookSettings = ({
|
||||
)
|
||||
|
||||
const setLocalWebhook = async (newLocalWebhook: Webhook) => {
|
||||
if (options.webhook) {
|
||||
onOptionsChange({ ...options, webhook: newLocalWebhook })
|
||||
return
|
||||
}
|
||||
_setLocalWebhook(newLocalWebhook)
|
||||
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
|
||||
}
|
||||
|
||||
const updateUrl = (url?: string) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, url: url ?? null })
|
||||
const updateUrl = (url: string) => {
|
||||
if (options.webhook)
|
||||
onOptionsChange({ ...options, webhook: { ...options.webhook, url } })
|
||||
else if (localWebhook)
|
||||
setLocalWebhook({ ...localWebhook, url: url ?? undefined })
|
||||
}
|
||||
|
||||
if (!localWebhook) return <Spinner />
|
||||
if (!localWebhook && !options.webhook) return <Spinner />
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<TextInput
|
||||
placeholder="Paste webhook URL..."
|
||||
defaultValue={localWebhook.url ?? ''}
|
||||
defaultValue={options.webhook?.url ?? localWebhook?.url ?? ''}
|
||||
onChange={updateUrl}
|
||||
/>
|
||||
<WebhookAdvancedConfigForm
|
||||
blockId={blockId}
|
||||
webhook={localWebhook}
|
||||
webhook={(options.webhook ?? localWebhook) as Webhook}
|
||||
options={options}
|
||||
onWebhookChange={setLocalWebhook}
|
||||
onOptionsChange={onOptionsChange}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
createWebhook,
|
||||
importTypebotInDatabase,
|
||||
} from '@typebot.io/lib/playwright/databaseActions'
|
||||
import { HttpMethod } from '@typebot.io/schemas'
|
||||
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { getTestAsset } from '@/test/utils/playwright'
|
||||
import { apiToken } from '@typebot.io/lib/playwright/databaseSetup'
|
||||
@@ -26,7 +26,8 @@ test.describe('Builder', () => {
|
||||
)
|
||||
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"`, {timeout: 10000}
|
||||
`"Group #1": "answer value", "Group #2": "20", "Group #2 (1)": "Yes"`,
|
||||
{ timeout: 10000 }
|
||||
)
|
||||
})
|
||||
|
||||
@@ -126,6 +127,7 @@ test.describe('API', () => {
|
||||
expect(webhookBlocks[0]).toEqual({
|
||||
id: 'webhookBlock',
|
||||
label: 'Webhook > webhookBlock',
|
||||
type: 'Webhook',
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ type Props = {
|
||||
|
||||
export const ZapierContent = ({ block }: Props) => {
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(block.webhookId))
|
||||
const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
|
||||
@@ -22,10 +22,17 @@ export const ZapierSettings = ({
|
||||
|
||||
const setLocalWebhook = useCallback(
|
||||
async (newLocalWebhook: Webhook) => {
|
||||
if (options.webhook) {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
webhook: newLocalWebhook,
|
||||
})
|
||||
return
|
||||
}
|
||||
_setLocalWebhook(newLocalWebhook)
|
||||
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
|
||||
},
|
||||
[updateWebhook]
|
||||
[onOptionsChange, options, updateWebhook]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -33,20 +40,23 @@ export const ZapierSettings = ({
|
||||
!localWebhook ||
|
||||
localWebhook.url ||
|
||||
!webhook?.url ||
|
||||
webhook.url === localWebhook.url
|
||||
webhook.url === localWebhook.url ||
|
||||
options.webhook
|
||||
)
|
||||
return
|
||||
setLocalWebhook({
|
||||
...localWebhook,
|
||||
url: webhook?.url,
|
||||
})
|
||||
}, [webhook, localWebhook, setLocalWebhook])
|
||||
}, [webhook, localWebhook, setLocalWebhook, options.webhook])
|
||||
|
||||
const url = options.webhook?.url ?? localWebhook?.url
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Alert status={localWebhook?.url ? 'success' : 'info'} rounded="md">
|
||||
<Alert status={url ? 'success' : 'info'} rounded="md">
|
||||
<AlertIcon />
|
||||
{localWebhook?.url ? (
|
||||
{url ? (
|
||||
<>Your zap is correctly configured 🚀</>
|
||||
) : (
|
||||
<Stack>
|
||||
@@ -62,10 +72,10 @@ export const ZapierSettings = ({
|
||||
</Stack>
|
||||
)}
|
||||
</Alert>
|
||||
{localWebhook && (
|
||||
{(localWebhook || options.webhook) && (
|
||||
<WebhookAdvancedConfigForm
|
||||
blockId={blockId}
|
||||
webhook={localWebhook}
|
||||
webhook={(options.webhook ?? localWebhook) as Webhook}
|
||||
options={options}
|
||||
onWebhookChange={setLocalWebhook}
|
||||
onOptionsChange={onOptionsChange}
|
||||
|
||||
@@ -23,7 +23,7 @@ test.describe('Condition block', () => {
|
||||
await page.click('button:has-text("Age")')
|
||||
await page.click('button:has-text("Select an operator")')
|
||||
await page.click('button:has-text("Greater than")', { force: true })
|
||||
await page.fill('input[placeholder="Type a value..."]', '80')
|
||||
await page.fill('input[placeholder="Type a number..."]', '80')
|
||||
|
||||
await page.click('button:has-text("Add a comparison")')
|
||||
|
||||
@@ -35,7 +35,7 @@ test.describe('Condition block', () => {
|
||||
await page.click('button:has-text("Select an operator")')
|
||||
await page.click('button:has-text("Less than")', { force: true })
|
||||
await page.fill(
|
||||
':nth-match(input[placeholder="Type a value..."], 2)',
|
||||
':nth-match(input[placeholder="Type a number..."], 2)',
|
||||
'100'
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ test.describe('Condition block', () => {
|
||||
await page.click('button:has-text("Age")')
|
||||
await page.click('button:has-text("Select an operator")')
|
||||
await page.click('button:has-text("Greater than")', { force: true })
|
||||
await page.fill('input[placeholder="Type a value..."]', '20')
|
||||
await page.fill('input[placeholder="Type a number..."]', '20')
|
||||
|
||||
await page.click('text=Preview')
|
||||
await page
|
||||
|
||||
@@ -49,7 +49,7 @@ export const parseNewTypebot = ({
|
||||
return {
|
||||
folderId,
|
||||
name,
|
||||
version: '3',
|
||||
version: '4',
|
||||
workspaceId,
|
||||
groups: [startGroup],
|
||||
edges: [],
|
||||
|
||||
@@ -29,13 +29,19 @@ export const importTypebotQuery = async (typebot: Typebot, userPlan: Plan) => {
|
||||
const webhookBlocks = typebot.groups
|
||||
.flatMap((b) => b.blocks)
|
||||
.filter(isWebhookBlock)
|
||||
.filter((block) => block.webhookId)
|
||||
await Promise.all(
|
||||
webhookBlocks.map((s) =>
|
||||
webhookBlocks.map((webhookBlock) =>
|
||||
duplicateWebhookQuery({
|
||||
existingIds: { typebotId: typebot.id, webhookId: s.webhookId },
|
||||
existingIds: {
|
||||
typebotId: typebot.id,
|
||||
webhookId: webhookBlock.webhookId as string,
|
||||
},
|
||||
newIds: {
|
||||
typebotId: newTypebot.id,
|
||||
webhookId: webhookIdsMapping.get(s.webhookId) as string,
|
||||
webhookId: webhookIdsMapping.get(
|
||||
webhookBlock.webhookId as string
|
||||
) as string,
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -51,84 +57,98 @@ const duplicateTypebot = (
|
||||
webhookIdsMapping: Map<string, string>
|
||||
} => {
|
||||
const groupIdsMapping = generateOldNewIdsMapping(typebot.groups)
|
||||
const blockIdsMapping = generateOldNewIdsMapping(
|
||||
typebot.groups.flatMap((group) => group.blocks)
|
||||
)
|
||||
const edgeIdsMapping = generateOldNewIdsMapping(typebot.edges)
|
||||
const webhookIdsMapping = generateOldNewIdsMapping(
|
||||
typebot.groups
|
||||
.flatMap((b) => b.blocks)
|
||||
.flatMap((group) => group.blocks)
|
||||
.filter(isWebhookBlock)
|
||||
.map((s) => ({ id: s.webhookId }))
|
||||
.map((block) => ({
|
||||
id: block.webhookId as string,
|
||||
}))
|
||||
)
|
||||
const id = createId()
|
||||
return {
|
||||
typebot: {
|
||||
...typebot,
|
||||
version: '3',
|
||||
id,
|
||||
name: `${typebot.name} copy`,
|
||||
publicId: null,
|
||||
customDomain: null,
|
||||
groups: typebot.groups.map((b) => ({
|
||||
...b,
|
||||
id: groupIdsMapping.get(b.id) as string,
|
||||
blocks: b.blocks.map((s) => {
|
||||
groups: typebot.groups.map((group) => ({
|
||||
...group,
|
||||
id: groupIdsMapping.get(group.id) as string,
|
||||
blocks: group.blocks.map((block) => {
|
||||
const newIds = {
|
||||
groupId: groupIdsMapping.get(s.groupId) as string,
|
||||
outgoingEdgeId: s.outgoingEdgeId
|
||||
? edgeIdsMapping.get(s.outgoingEdgeId)
|
||||
id: blockIdsMapping.get(block.id) as string,
|
||||
groupId: groupIdsMapping.get(block.groupId) as string,
|
||||
outgoingEdgeId: block.outgoingEdgeId
|
||||
? edgeIdsMapping.get(block.outgoingEdgeId)
|
||||
: undefined,
|
||||
}
|
||||
if (
|
||||
s.type === LogicBlockType.TYPEBOT_LINK &&
|
||||
s.options.typebotId === 'current' &&
|
||||
isDefined(s.options.groupId)
|
||||
block.type === LogicBlockType.TYPEBOT_LINK &&
|
||||
block.options.typebotId === 'current' &&
|
||||
isDefined(block.options.groupId)
|
||||
)
|
||||
return {
|
||||
...s,
|
||||
...block,
|
||||
...newIds,
|
||||
options: {
|
||||
...s.options,
|
||||
groupId: groupIdsMapping.get(s.options.groupId as string),
|
||||
...block.options,
|
||||
groupId: groupIdsMapping.get(block.options.groupId as string),
|
||||
},
|
||||
}
|
||||
if (s.type === LogicBlockType.JUMP)
|
||||
if (block.type === LogicBlockType.JUMP)
|
||||
return {
|
||||
...s,
|
||||
...block,
|
||||
...newIds,
|
||||
options: {
|
||||
...s.options,
|
||||
groupId: groupIdsMapping.get(s.options.groupId as string),
|
||||
...block.options,
|
||||
groupId: groupIdsMapping.get(block.options.groupId as string),
|
||||
} satisfies JumpBlock['options'],
|
||||
}
|
||||
if (blockHasItems(s))
|
||||
if (blockHasItems(block))
|
||||
return {
|
||||
...s,
|
||||
items: s.items.map((item) => ({
|
||||
...block,
|
||||
...newIds,
|
||||
items: block.items.map((item) => ({
|
||||
...item,
|
||||
outgoingEdgeId: item.outgoingEdgeId
|
||||
? (edgeIdsMapping.get(item.outgoingEdgeId) as string)
|
||||
: undefined,
|
||||
})),
|
||||
...newIds,
|
||||
} as ChoiceInputBlock | ConditionBlock
|
||||
if (isWebhookBlock(s)) {
|
||||
if (isWebhookBlock(block) && block.webhookId) {
|
||||
return {
|
||||
...s,
|
||||
webhookId: webhookIdsMapping.get(s.webhookId) as string,
|
||||
...block,
|
||||
...newIds,
|
||||
webhookId: webhookIdsMapping.get(block.webhookId) as string,
|
||||
}
|
||||
}
|
||||
return {
|
||||
...s,
|
||||
...block,
|
||||
...newIds,
|
||||
}
|
||||
}),
|
||||
})),
|
||||
edges: typebot.edges.map((e) => ({
|
||||
...e,
|
||||
id: edgeIdsMapping.get(e.id) as string,
|
||||
edges: typebot.edges.map((edge) => ({
|
||||
...edge,
|
||||
id: edgeIdsMapping.get(edge.id) as string,
|
||||
from: {
|
||||
...e.from,
|
||||
groupId: groupIdsMapping.get(e.from.groupId) as string,
|
||||
...edge.from,
|
||||
blockId: blockIdsMapping.get(edge.from.blockId) as string,
|
||||
groupId: groupIdsMapping.get(edge.from.groupId) as string,
|
||||
},
|
||||
to: {
|
||||
...edge.to,
|
||||
blockId: edge.to.blockId
|
||||
? (blockIdsMapping.get(edge.to.blockId) as string)
|
||||
: undefined,
|
||||
groupId: groupIdsMapping.get(edge.to.groupId) as string,
|
||||
},
|
||||
to: { ...e.to, groupId: groupIdsMapping.get(e.to.groupId) as string },
|
||||
})),
|
||||
settings:
|
||||
typebot.settings.general.isBrandingEnabled === false &&
|
||||
|
||||
@@ -189,7 +189,8 @@ export const duplicateBlockDraft =
|
||||
} as Block
|
||||
if (isWebhookBlock(block)) {
|
||||
const newWebhookId = createId()
|
||||
onWebhookBlockDuplicated(block.webhookId, newWebhookId)
|
||||
if (block.webhookId)
|
||||
onWebhookBlockDuplicated(block.webhookId, newWebhookId)
|
||||
return {
|
||||
...block,
|
||||
groupId,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { createId } from '@paralleldrive/cuid2'
|
||||
import {
|
||||
isBubbleBlockType,
|
||||
blockTypeHasOption,
|
||||
blockTypeHasWebhook,
|
||||
blockTypeHasItems,
|
||||
} from '@typebot.io/lib'
|
||||
import {
|
||||
@@ -134,7 +133,7 @@ const parseDefaultBlockOptions = (type: BlockWithOptionsType): BlockOptions => {
|
||||
case IntegrationBlockType.PABBLY_CONNECT:
|
||||
case IntegrationBlockType.MAKE_COM:
|
||||
case IntegrationBlockType.WEBHOOK:
|
||||
return defaultWebhookOptions
|
||||
return defaultWebhookOptions(createId())
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return defaultSendEmailOptions
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
@@ -159,7 +158,6 @@ export const parseNewBlock = (
|
||||
options: blockTypeHasOption(type)
|
||||
? parseDefaultBlockOptions(type)
|
||||
: undefined,
|
||||
webhookId: blockTypeHasWebhook(type) ? createId() : undefined,
|
||||
items: blockTypeHasItems(type) ? parseDefaultItems(type, id) : undefined,
|
||||
} as DraggableBlock
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { isReadTypebotForbidden } from '@/features/typebot/helpers/isReadTypebot
|
||||
import { removeTypebotOldProperties } from '@/features/typebot/helpers/removeTypebotOldProperties'
|
||||
import { roundGroupsCoordinate } from '@/features/typebot/helpers/roundGroupsCoordinate'
|
||||
import { archiveResults } from '@typebot.io/lib/api/helpers/archiveResults'
|
||||
import { migrateTypebotFromV3ToV4 } from '@typebot.io/lib/migrations/migrateTypebotFromV3ToV4'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await getAuthenticatedUser(req, res)
|
||||
@@ -37,9 +38,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
collaborators.find((c) => c.userId === user.id)?.type ===
|
||||
CollaborationType.READ
|
||||
return res.send({
|
||||
typebot: roundGroupsCoordinate(
|
||||
removeTypebotOldProperties(typebot) as Typebot
|
||||
),
|
||||
typebot: await migrateTypebot(typebot as Typebot),
|
||||
publishedTypebot,
|
||||
isReadOnly,
|
||||
webhooks,
|
||||
@@ -95,7 +94,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
|
||||
const updates = {
|
||||
...omit(data, 'id', 'createdAt', 'updatedAt'),
|
||||
version: '3',
|
||||
theme: data.theme ?? undefined,
|
||||
settings: data.settings ?? undefined,
|
||||
resultsTablePreferences: data.resultsTablePreferences ?? undefined,
|
||||
@@ -142,4 +140,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
const migrateTypebot = async (typebot: Typebot): Promise<Typebot> => {
|
||||
if (typebot.version === '4') return typebot
|
||||
const updatedTypebot = roundGroupsCoordinate(
|
||||
removeTypebotOldProperties(typebot) as Typebot
|
||||
)
|
||||
return migrateTypebotFromV3ToV4(prisma)(updatedTypebot)
|
||||
}
|
||||
|
||||
export default handler
|
||||
|
||||
Reference in New Issue
Block a user