2
0

refactor: ♻️ Rename step to block

This commit is contained in:
Baptiste Arnaud
2022-06-11 07:27:38 +02:00
parent 8751766d0e
commit 2df8338505
297 changed files with 4292 additions and 3989 deletions

View File

@@ -17,14 +17,17 @@ export const getServerSideProps: GetServerSideProps = async (
try {
if (!host) return { props: {} }
const viewerUrls = (process.env.NEXT_PUBLIC_VIEWER_URL ?? '').split(',')
const isMatchingViewerUrl = viewerUrls.some(
(url) =>
host.split(':')[0].includes(url.split('//')[1].split(':')[0]) ||
(forwardedHost &&
forwardedHost
.split(':')[0]
.includes(url.split('//')[1].split(':')[0]))
)
const isMatchingViewerUrl =
process.env.NEXT_PUBLIC_E2E_TEST === 'enabled'
? true
: viewerUrls.some(
(url) =>
host.split(':')[0].includes(url.split('//')[1].split(':')[0]) ||
(forwardedHost &&
forwardedHost
.split(':')[0]
.includes(url.split('//')[1].split(':')[0]))
)
const customDomain = `${forwardedHost ?? host}${
pathname === '/' ? '' : pathname
}`

View File

@@ -10,7 +10,7 @@ import {
Webhook,
WebhookOptions,
WebhookResponse,
WebhookStep,
WebhookBlock,
} from 'models'
import { parseVariables } from 'bot-engine'
import { NextApiRequest, NextApiResponse } from 'next'
@@ -38,7 +38,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await cors(req, res)
if (req.method === 'POST') {
const typebotId = req.query.typebotId.toString()
const stepId = req.query.blockId.toString()
const blockId = req.query.blockId.toString()
const resultId = req.query.resultId as string | undefined
const { resultValues, variables } = (
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
@@ -51,19 +51,19 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
include: { webhooks: true },
})) as unknown as (Typebot & { webhooks: Webhook[] }) | null
if (!typebot) return notFound(res)
const step = typebot.blocks
.flatMap((b) => b.steps)
.find(byId(stepId)) as WebhookStep
const webhook = typebot.webhooks.find(byId(step.webhookId))
const block = typebot.groups
.flatMap((g) => g.blocks)
.find(byId(blockId)) as WebhookBlock
const webhook = typebot.webhooks.find(byId(block.webhookId))
if (!webhook)
return res
.status(404)
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
const preparedWebhook = prepareWebhookAttributes(webhook, step.options)
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
const result = await executeWebhook(typebot)(
preparedWebhook,
variables,
step.blockId,
block.groupId,
resultValues,
resultId
)
@@ -89,7 +89,7 @@ export const executeWebhook =
async (
webhook: Webhook,
variables: Variable[],
blockId: string,
groupId: string,
resultValues?: ResultValues,
resultId?: string
): Promise<WebhookResponse> => {
@@ -130,7 +130,7 @@ export const executeWebhook =
)({
body: webhook.body,
resultValues,
blockId,
groupId,
})
: undefined
const request = {
@@ -191,33 +191,33 @@ export const executeWebhook =
const getBodyContent =
(
typebot: Pick<Typebot | PublicTypebot, 'blocks' | 'variables' | 'edges'>,
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>,
linkedTypebots: (Typebot | PublicTypebot)[]
) =>
async ({
body,
resultValues,
blockId,
groupId,
}: {
body?: string | null
resultValues?: ResultValues
blockId: string
groupId: string
}): Promise<string | undefined> => {
if (!body) return
return body === '{{state}}'
? JSON.stringify(
resultValues
? parseAnswers({
blocks: [
...typebot.blocks,
...linkedTypebots.flatMap((t) => t.blocks),
groups: [
...typebot.groups,
...linkedTypebots.flatMap((t) => t.groups),
],
variables: [
...typebot.variables,
...linkedTypebots.flatMap((t) => t.variables),
],
})(resultValues)
: await parseSampleResult(typebot, linkedTypebots)(blockId)
: await parseSampleResult(typebot, linkedTypebots)(groupId)
)
: body
}

View File

@@ -10,7 +10,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (!user) return res.status(401).json({ message: 'Not authenticated' })
if (req.method === 'GET') {
const typebotId = req.query.typebotId.toString()
const stepId = req.query.blockId.toString()
const blockId = req.query.blockId.toString()
const typebot = (await prisma.typebot.findFirst({
where: {
id: typebotId,
@@ -18,13 +18,13 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
},
})) as unknown as Typebot | undefined
if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
const step = typebot.blocks
.flatMap((b) => b.steps)
.find((s) => s.id === stepId)
if (!step) return res.status(404).send({ message: 'Block not found' })
const block = typebot.groups
.flatMap((g) => g.blocks)
.find((s) => s.id === blockId)
if (!block) return res.status(404).send({ message: 'Group not found' })
const linkedTypebots = await getLinkedTypebots(typebot, user)
return res.send(
await parseSampleResult(typebot, linkedTypebots)(step.blockId)
await parseSampleResult(typebot, linkedTypebots)(block.groupId)
)
}
methodNotAllowed(res)

View File

@@ -6,7 +6,7 @@ import {
Variable,
Webhook,
WebhookOptions,
WebhookStep,
WebhookBlock,
} from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { byId, initMiddleware, methodNotAllowed, notFound } from 'utils'
@@ -19,8 +19,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await cors(req, res)
if (req.method === 'POST') {
const typebotId = req.query.typebotId.toString()
const groupId = req.query.groupId.toString()
const blockId = req.query.blockId.toString()
const stepId = req.query.stepId.toString()
const resultId = req.query.resultId as string | undefined
const { resultValues, variables } = (
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
@@ -33,19 +33,19 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
include: { webhooks: true },
})) as unknown as (Typebot & { webhooks: Webhook[] }) | null
if (!typebot) return notFound(res)
const step = typebot.blocks
.find(byId(blockId))
?.steps.find(byId(stepId)) as WebhookStep
const webhook = typebot.webhooks.find(byId(step.webhookId))
const block = typebot.groups
.find(byId(groupId))
?.blocks.find(byId(blockId)) as WebhookBlock
const webhook = typebot.webhooks.find(byId(block.webhookId))
if (!webhook)
return res
.status(404)
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
const preparedWebhook = prepareWebhookAttributes(webhook, step.options)
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
const result = await executeWebhook(typebot)(
preparedWebhook,
variables,
blockId,
groupId,
resultValues,
resultId
)

View File

@@ -10,7 +10,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (!user) return res.status(401).json({ message: 'Not authenticated' })
if (req.method === 'GET') {
const typebotId = req.query.typebotId.toString()
const blockId = req.query.blockId.toString()
const groupId = req.query.groupId.toString()
const typebot = (await prisma.typebot.findFirst({
where: {
id: typebotId,
@@ -19,7 +19,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
})) as unknown as Typebot | undefined
if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
const linkedTypebots = await getLinkedTypebots(typebot, user)
return res.send(await parseSampleResult(typebot, linkedTypebots)(blockId))
return res.send(await parseSampleResult(typebot, linkedTypebots)(groupId))
}
methodNotAllowed(res)
}

View File

@@ -1,6 +1,6 @@
import { withSentry } from '@sentry/nextjs'
import prisma from 'libs/prisma'
import { Typebot, WebhookStep } from 'models'
import { Typebot, WebhookBlock } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { byId, methodNotAllowed } from 'utils'
@@ -14,8 +14,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(403).send({ message: 'url is missing in body' })
const { url } = body
const typebotId = req.query.typebotId.toString()
const groupId = req.query.groupId.toString()
const blockId = req.query.blockId.toString()
const stepId = req.query.stepId.toString()
const typebot = (await prisma.typebot.findFirst({
where: {
id: typebotId,
@@ -24,9 +24,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
})) as unknown as Typebot | undefined
if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
try {
const { webhookId } = typebot.blocks
.find(byId(blockId))
?.steps.find(byId(stepId)) as WebhookStep
const { webhookId } = typebot.groups
.find(byId(groupId))
?.blocks.find(byId(blockId)) as WebhookBlock
await prisma.webhook.upsert({
where: { id: webhookId },
update: { url, body: '{{state}}', method: 'POST' },
@@ -37,7 +37,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} catch (err) {
return res
.status(400)
.send({ message: "stepId doesn't point to a Webhook step" })
.send({ message: "blockId doesn't point to a Webhook block" })
}
}
return methodNotAllowed(res)

View File

@@ -1,6 +1,6 @@
import { withSentry } from '@sentry/nextjs'
import prisma from 'libs/prisma'
import { Typebot, WebhookStep } from 'models'
import { Typebot, WebhookBlock } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { byId, methodNotAllowed } from 'utils'
@@ -10,8 +10,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (!user) return res.status(401).json({ message: 'Not authenticated' })
if (req.method === 'POST') {
const typebotId = req.query.typebotId.toString()
const groupId = req.query.groupId.toString()
const blockId = req.query.blockId.toString()
const stepId = req.query.stepId.toString()
const typebot = (await prisma.typebot.findFirst({
where: {
id: typebotId,
@@ -20,9 +20,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
})) as unknown as Typebot | undefined
if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
try {
const { webhookId } = typebot.blocks
.find(byId(blockId))
?.steps.find(byId(stepId)) as WebhookStep
const { webhookId } = typebot.groups
.find(byId(groupId))
?.blocks.find(byId(blockId)) as WebhookBlock
await prisma.webhook.update({
where: { id: webhookId },
data: { url: null },
@@ -32,7 +32,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} catch (err) {
return res
.status(400)
.send({ message: "stepId doesn't point to a Webhook step" })
.send({ message: "blockId doesn't point to a Webhook block" })
}
}
return methodNotAllowed(res)

View File

@@ -1,6 +1,6 @@
import { withSentry } from '@sentry/nextjs'
import prisma from 'libs/prisma'
import { Typebot, WebhookStep } from 'models'
import { Typebot, WebhookBlock } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { byId, methodNotAllowed } from 'utils'
@@ -14,7 +14,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(403).send({ message: 'url is missing in body' })
const { url } = body
const typebotId = req.query.typebotId.toString()
const stepId = req.query.blockId.toString()
const blockId = req.query.blockId.toString()
const typebot = (await prisma.typebot.findFirst({
where: {
id: typebotId,
@@ -23,9 +23,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
})) as unknown as Typebot | undefined
if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
try {
const { webhookId } = typebot.blocks
.flatMap((b) => b.steps)
.find(byId(stepId)) as WebhookStep
const { webhookId } = typebot.groups
.flatMap((g) => g.blocks)
.find(byId(blockId)) as WebhookBlock
await prisma.webhook.upsert({
where: { id: webhookId },
update: { url, body: '{{state}}', method: 'POST' },
@@ -36,7 +36,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} catch (err) {
return res
.status(400)
.send({ message: "blockId doesn't point to a Webhook step" })
.send({ message: "groupId doesn't point to a Webhook block" })
}
}
return methodNotAllowed(res)

View File

@@ -1,6 +1,6 @@
import { withSentry } from '@sentry/nextjs'
import prisma from 'libs/prisma'
import { Typebot, WebhookStep } from 'models'
import { Typebot, WebhookBlock } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { byId, methodNotAllowed } from 'utils'
@@ -10,7 +10,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (!user) return res.status(401).json({ message: 'Not authenticated' })
if (req.method === 'POST') {
const typebotId = req.query.typebotId.toString()
const stepId = req.query.blockId.toString()
const blockId = req.query.blockId.toString()
const typebot = (await prisma.typebot.findFirst({
where: {
id: typebotId,
@@ -19,9 +19,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
})) as unknown as Typebot | undefined
if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
try {
const { webhookId } = typebot.blocks
.flatMap((b) => b.steps)
.find(byId(stepId)) as WebhookStep
const { webhookId } = typebot.groups
.flatMap((g) => g.blocks)
.find(byId(blockId)) as WebhookBlock
await prisma.webhook.update({
where: { id: webhookId },
data: { url: null },
@@ -31,7 +31,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} catch (err) {
return res
.status(400)
.send({ message: "blockId doesn't point to a Webhook step" })
.send({ message: "groupId doesn't point to a Webhook block" })
}
}
return methodNotAllowed(res)

View File

@@ -11,10 +11,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
) as Answer
const result = await prisma.answer.upsert({
where: {
resultId_blockId_stepId: {
resultId_blockId_groupId: {
resultId: answer.resultId,
groupId: answer.groupId,
blockId: answer.blockId,
stepId: answer.stepId,
},
},
create: answer,

View File

@@ -1,9 +1,9 @@
import { withSentry } from '@sentry/nextjs'
import prisma from 'libs/prisma'
import { Block, WebhookStep } from 'models'
import { Group, WebhookBlock } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { byId, isWebhookStep, methodNotAllowed } from 'utils'
import { byId, isWebhookBlock, methodNotAllowed } from 'utils'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
@@ -15,24 +15,25 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
id: typebotId,
workspace: { members: { some: { userId: user.id } } },
},
select: { blocks: true, webhooks: true },
select: { groups: true, webhooks: true },
})
const emptyWebhookSteps = (typebot?.blocks as Block[]).reduce<
const emptyWebhookBlocks = (typebot?.groups as Group[]).reduce<
{ blockId: string; name: string; url: string | undefined }[]
>((emptyWebhookSteps, block) => {
const steps = block.steps.filter((step) =>
isWebhookStep(step)
) as WebhookStep[]
>((emptyWebhookBlocks, group) => {
const blocks = group.blocks.filter((block) =>
isWebhookBlock(block)
) as WebhookBlock[]
return [
...emptyWebhookSteps,
...steps.map((s) => ({
blockId: s.id,
name: `${block.title} > ${s.id}`,
url: typebot?.webhooks.find(byId(s.webhookId))?.url ?? undefined,
...emptyWebhookBlocks,
...blocks.map((b) => ({
blockId: b.id,
name: `${group.title} > ${b.id}`,
url: typebot?.webhooks.find(byId(b.webhookId))?.url ?? undefined,
})),
]
}, [])
return res.send({ blocks: emptyWebhookSteps })
console.log({ blocks: emptyWebhookBlocks })
return res.send({ blocks: emptyWebhookBlocks })
}
return methodNotAllowed(res)
}

View File

@@ -1,9 +1,9 @@
import { withSentry } from '@sentry/nextjs'
import prisma from 'libs/prisma'
import { Block } from 'models'
import { Group, WebhookBlock } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { byId, isNotDefined, isWebhookStep, methodNotAllowed } from 'utils'
import { byId, isNotDefined, isWebhookBlock, methodNotAllowed } from 'utils'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
@@ -15,26 +15,28 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
id: typebotId,
workspace: { members: { some: { userId: user.id } } },
},
select: { blocks: true, webhooks: true },
select: { groups: true, webhooks: true },
})
const emptyWebhookSteps = (typebot?.blocks as Block[]).reduce<
{ blockId: string; id: string; name: string }[]
>((emptyWebhookSteps, block) => {
const steps = block.steps.filter(
(step) =>
isWebhookStep(step) &&
isNotDefined(typebot?.webhooks.find(byId(step.webhookId))?.url)
const emptyWebhookBlocks = (typebot?.groups as Group[]).reduce<
{ groupId: string; id: string; name: string }[]
>((emptyWebhookBlocks, group) => {
const blocks = group.blocks.filter(
(block) =>
isWebhookBlock(block) &&
isNotDefined(
typebot?.webhooks.find(byId((block as WebhookBlock).webhookId))?.url
)
)
return [
...emptyWebhookSteps,
...steps.map((s) => ({
...emptyWebhookBlocks,
...blocks.map((s) => ({
id: s.id,
blockId: s.blockId,
name: `${block.title} > ${s.id}`,
groupId: s.groupId,
name: `${group.title} > ${s.id}`,
})),
]
}, [])
return res.send({ steps: emptyWebhookSteps })
return res.send({ blocks: emptyWebhookBlocks })
}
return methodNotAllowed(res)
}

View File

@@ -5,15 +5,15 @@
"name": "My typebot",
"publishedTypebotId": null,
"folderId": null,
"blocks": [
"groups": [
{
"id": "k6kY6gwRE6noPoYQNGzgUq",
"steps": [
"blocks": [
{
"id": "22HP69iipkLjJDTUcc1AWW",
"type": "start",
"label": "Start",
"blockId": "k6kY6gwRE6noPoYQNGzgUq",
"groupId": "k6kY6gwRE6noPoYQNGzgUq",
"outgoingEdgeId": "oNvqaqNExdSH2kKEhKZHuE"
}
],
@@ -22,11 +22,11 @@
},
{
"id": "kinRXxYop2X4d7F9qt8WNB",
"steps": [
"blocks": [
{
"id": "sc1y8VwDabNJgiVTBi4qtif",
"type": "text",
"blockId": "kinRXxYop2X4d7F9qt8WNB",
"groupId": "kinRXxYop2X4d7F9qt8WNB",
"content": {
"html": "<div>Welcome to <span class=\"slate-bold\">AA</span> (Awesome Agency)</div>",
"richText": [
@@ -45,7 +45,7 @@
{
"id": "s7YqZTBeyCa4Hp3wN2j922c",
"type": "image",
"blockId": "kinRXxYop2X4d7F9qt8WNB",
"groupId": "kinRXxYop2X4d7F9qt8WNB",
"content": {
"url": "https://media2.giphy.com/media/XD9o33QG9BoMis7iM4/giphy.gif?cid=fe3852a3ihg8rvipzzky5lybmdyq38fhke2tkrnshwk52c7d&rid=giphy.gif&ct=g"
}
@@ -57,11 +57,11 @@
{
"id": "hQw2zbp7FDX7XYK9cFpbgC",
"type": 0,
"stepId": "sbjZWLJGVkHAkDqS4JQeGow",
"blockId": "sbjZWLJGVkHAkDqS4JQeGow",
"content": "Hi!"
}
],
"blockId": "kinRXxYop2X4d7F9qt8WNB",
"groupId": "kinRXxYop2X4d7F9qt8WNB",
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
"outgoingEdgeId": "i51YhHpk1dtSyduFNf5Wim"
}
@@ -71,11 +71,11 @@
},
{
"id": "o4SH1UtKANnW5N5D67oZUz",
"steps": [
"blocks": [
{
"id": "sxeYubYN6XzhAfG7m9Fivhc",
"type": "text",
"blockId": "o4SH1UtKANnW5N5D67oZUz",
"groupId": "o4SH1UtKANnW5N5D67oZUz",
"content": {
"html": "<div>Great! Nice to meet you {{Name}}</div>",
"richText": [
@@ -90,7 +90,7 @@
{
"id": "scQ5kduafAtfP9T8SHUJnGi",
"type": "text",
"blockId": "o4SH1UtKANnW5N5D67oZUz",
"groupId": "o4SH1UtKANnW5N5D67oZUz",
"content": {
"html": "<div>What&#x27;s the best email we can reach you at?</div>",
"richText": [
@@ -107,7 +107,7 @@
{
"id": "snbsad18Bgry8yZ8DZCfdFD",
"type": "email input",
"blockId": "o4SH1UtKANnW5N5D67oZUz",
"groupId": "o4SH1UtKANnW5N5D67oZUz",
"options": {
"labels": { "button": "Send", "placeholder": "Type your email..." },
"variableId": "3VFChNVSCXQ2rXv4DrJ8Ah"
@@ -120,11 +120,11 @@
},
{
"id": "q5dAhqSTCaNdiGSJm9B9Rw",
"steps": [
"blocks": [
{
"id": "sgtE2Sy7cKykac9B223Kq9R",
"type": "text",
"blockId": "q5dAhqSTCaNdiGSJm9B9Rw",
"groupId": "q5dAhqSTCaNdiGSJm9B9Rw",
"content": {
"html": "<div>What&#x27;s your name?</div>",
"richText": [
@@ -136,7 +136,7 @@
{
"id": "sqEsMo747LTDnY9FjQcEwUv",
"type": "text input",
"blockId": "q5dAhqSTCaNdiGSJm9B9Rw",
"groupId": "q5dAhqSTCaNdiGSJm9B9Rw",
"options": {
"isLong": false,
"labels": {
@@ -153,11 +153,11 @@
},
{
"id": "fKqRz7iswk7ULaj5PJocZL",
"steps": [
"blocks": [
{
"id": "su7HceVXWyTCzi2vv3m4QbK",
"type": "text",
"blockId": "fKqRz7iswk7ULaj5PJocZL",
"groupId": "fKqRz7iswk7ULaj5PJocZL",
"content": {
"html": "<div>What services are you interested in?</div>",
"richText": [
@@ -176,29 +176,29 @@
{
"id": "fnLCBF4NdraSwcubnBhk8H",
"type": 0,
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
"blockId": "s5VQGsVF4hQgziQsXVdwPDW",
"content": "Website dev"
},
{
"id": "a782h8ynMouY84QjH7XSnR",
"type": 0,
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
"blockId": "s5VQGsVF4hQgziQsXVdwPDW",
"content": "Content Marketing"
},
{
"id": "jGvh94zBByvVFpSS3w97zY",
"type": 0,
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
"blockId": "s5VQGsVF4hQgziQsXVdwPDW",
"content": "Social Media"
},
{
"id": "6PRLbKUezuFmwWtLVbvAQ7",
"type": 0,
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
"blockId": "s5VQGsVF4hQgziQsXVdwPDW",
"content": "UI / UX Design"
}
],
"blockId": "fKqRz7iswk7ULaj5PJocZL",
"groupId": "fKqRz7iswk7ULaj5PJocZL",
"options": { "buttonLabel": "Send", "isMultipleChoice": true },
"outgoingEdgeId": "ohTRakmcYJ7GdFWRZrWRjk"
}
@@ -208,11 +208,11 @@
},
{
"id": "7qHBEyCMvKEJryBHzPmHjV",
"steps": [
"blocks": [
{
"id": "sqR8Sz9gW21aUYKtUikq7qZ",
"type": "text",
"blockId": "7qHBEyCMvKEJryBHzPmHjV",
"groupId": "7qHBEyCMvKEJryBHzPmHjV",
"content": {
"html": "<div>Can you tell me a bit more about your needs?</div>",
"richText": [
@@ -229,7 +229,7 @@
{
"id": "sqFy2G3C1mh9p6s3QBdSS5x",
"type": "text input",
"blockId": "7qHBEyCMvKEJryBHzPmHjV",
"groupId": "7qHBEyCMvKEJryBHzPmHjV",
"options": {
"isLong": true,
"labels": { "button": "Send", "placeholder": "Type your answer..." }
@@ -242,11 +242,11 @@
},
{
"id": "vF7AD7zSAj7SNvN3gr9N94",
"steps": [
"blocks": [
{
"id": "seLegenCgUwMopRFeAefqZ7",
"type": "text",
"blockId": "vF7AD7zSAj7SNvN3gr9N94",
"groupId": "vF7AD7zSAj7SNvN3gr9N94",
"content": {
"html": "<div>Perfect!</div>",
"richText": [{ "type": "p", "children": [{ "text": "Perfect!" }] }],
@@ -256,7 +256,7 @@
{
"id": "s779Q1y51aVaDUJVrFb16vv",
"type": "text",
"blockId": "vF7AD7zSAj7SNvN3gr9N94",
"groupId": "vF7AD7zSAj7SNvN3gr9N94",
"content": {
"html": "<div>We&#x27;ll get back to you at {{Email}}</div>",
"richText": [
@@ -274,13 +274,13 @@
"graphCoordinates": { "x": 1668, "y": 143 }
},
{
"id": "webhookBlock",
"id": "webhookGroup",
"graphCoordinates": { "x": 1996, "y": 134 },
"title": "Webhook",
"steps": [
"blocks": [
{
"id": "webhookStep",
"blockId": "webhookBlock",
"id": "webhookBlock",
"groupId": "webhookGroup",
"type": "Webhook",
"options": { "responseVariableMapping": [], "variablesForTest": [] },
"webhookId": "webhook1"
@@ -295,58 +295,58 @@
"edges": [
{
"id": "oNvqaqNExdSH2kKEhKZHuE",
"to": { "blockId": "kinRXxYop2X4d7F9qt8WNB" },
"to": { "groupId": "kinRXxYop2X4d7F9qt8WNB" },
"from": {
"stepId": "22HP69iipkLjJDTUcc1AWW",
"blockId": "k6kY6gwRE6noPoYQNGzgUq"
"blockId": "22HP69iipkLjJDTUcc1AWW",
"groupId": "k6kY6gwRE6noPoYQNGzgUq"
}
},
{
"id": "i51YhHpk1dtSyduFNf5Wim",
"to": { "blockId": "q5dAhqSTCaNdiGSJm9B9Rw" },
"to": { "groupId": "q5dAhqSTCaNdiGSJm9B9Rw" },
"from": {
"stepId": "sbjZWLJGVkHAkDqS4JQeGow",
"blockId": "kinRXxYop2X4d7F9qt8WNB"
"blockId": "sbjZWLJGVkHAkDqS4JQeGow",
"groupId": "kinRXxYop2X4d7F9qt8WNB"
}
},
{
"id": "4tYbERpi5Po4goVgt6rWXg",
"to": { "blockId": "o4SH1UtKANnW5N5D67oZUz" },
"to": { "groupId": "o4SH1UtKANnW5N5D67oZUz" },
"from": {
"stepId": "sqEsMo747LTDnY9FjQcEwUv",
"blockId": "q5dAhqSTCaNdiGSJm9B9Rw"
"blockId": "sqEsMo747LTDnY9FjQcEwUv",
"groupId": "q5dAhqSTCaNdiGSJm9B9Rw"
}
},
{
"id": "w3MiN1Ct38jT5NykVsgmb5",
"to": { "blockId": "fKqRz7iswk7ULaj5PJocZL" },
"to": { "groupId": "fKqRz7iswk7ULaj5PJocZL" },
"from": {
"stepId": "snbsad18Bgry8yZ8DZCfdFD",
"blockId": "o4SH1UtKANnW5N5D67oZUz"
"blockId": "snbsad18Bgry8yZ8DZCfdFD",
"groupId": "o4SH1UtKANnW5N5D67oZUz"
}
},
{
"id": "ohTRakmcYJ7GdFWRZrWRjk",
"to": { "blockId": "7qHBEyCMvKEJryBHzPmHjV" },
"to": { "groupId": "7qHBEyCMvKEJryBHzPmHjV" },
"from": {
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
"blockId": "fKqRz7iswk7ULaj5PJocZL"
"blockId": "s5VQGsVF4hQgziQsXVdwPDW",
"groupId": "fKqRz7iswk7ULaj5PJocZL"
}
},
{
"id": "sH5nUssG2XQbm6ZidGv9BY",
"to": { "blockId": "vF7AD7zSAj7SNvN3gr9N94" },
"to": { "groupId": "vF7AD7zSAj7SNvN3gr9N94" },
"from": {
"stepId": "sqFy2G3C1mh9p6s3QBdSS5x",
"blockId": "7qHBEyCMvKEJryBHzPmHjV"
"blockId": "sqFy2G3C1mh9p6s3QBdSS5x",
"groupId": "7qHBEyCMvKEJryBHzPmHjV"
}
},
{
"from": {
"blockId": "vF7AD7zSAj7SNvN3gr9N94",
"stepId": "s779Q1y51aVaDUJVrFb16vv"
"groupId": "vF7AD7zSAj7SNvN3gr9N94",
"blockId": "s779Q1y51aVaDUJVrFb16vv"
},
"to": { "blockId": "webhookBlock" },
"to": { "groupId": "webhookGroup" },
"id": "fTVo43AG97eKcaTrZf9KyV"
}
],

View File

@@ -5,15 +5,15 @@
"name": "My typebot",
"publishedTypebotId": "dm12bh6hmEQemywn86osJD",
"folderId": null,
"blocks": [
"groups": [
{
"id": "dqork4dJJZk3RgKYavBpRE",
"steps": [
"blocks": [
{
"id": "u7Px8eD9MWXNJEBwxQwJCF",
"type": "start",
"label": "Start",
"blockId": "dqork4dJJZk3RgKYavBpRE",
"groupId": "dqork4dJJZk3RgKYavBpRE",
"outgoingEdgeId": "b3XsreaqtWt4CrZZmCKDpa"
}
],
@@ -22,11 +22,11 @@
},
{
"id": "2Vrpgk5VP9BUo3vKtM5kws",
"steps": [
"blocks": [
{
"id": "s6GezsfD612D1naKwvhDFgA",
"type": "text",
"blockId": "2Vrpgk5VP9BUo3vKtM5kws",
"groupId": "2Vrpgk5VP9BUo3vKtM5kws",
"content": {
"html": "<div>Hi what&#x27;s your name?</div>",
"richText": [
@@ -38,7 +38,7 @@
{
"id": "sq3mPXUrugs5t6FoME3T4t4",
"type": "text input",
"blockId": "2Vrpgk5VP9BUo3vKtM5kws",
"groupId": "2Vrpgk5VP9BUo3vKtM5kws",
"options": {
"isLong": false,
"labels": {
@@ -51,7 +51,7 @@
{
"id": "s57hbzfpG2sVvXefznVhbVB",
"type": "text",
"blockId": "2Vrpgk5VP9BUo3vKtM5kws",
"groupId": "2Vrpgk5VP9BUo3vKtM5kws",
"content": {
"html": "<div>How old are you?</div>",
"richText": [
@@ -63,7 +63,7 @@
{
"id": "s4fFn3s7nouQk88iJ3oLgx6",
"type": "number input",
"blockId": "2Vrpgk5VP9BUo3vKtM5kws",
"groupId": "2Vrpgk5VP9BUo3vKtM5kws",
"options": {
"labels": { "button": "Send", "placeholder": "Type a number..." }
}
@@ -71,7 +71,7 @@
{
"id": "scR6MewJwkPNJzABYG8NEA4",
"type": "text",
"blockId": "2Vrpgk5VP9BUo3vKtM5kws",
"groupId": "2Vrpgk5VP9BUo3vKtM5kws",
"content": {
"html": "<div>Cool!</div>",
"richText": [{ "type": "p", "children": [{ "text": "Cool!" }] }],
@@ -81,7 +81,7 @@
{
"id": "s5fo1s8UTyHQ7CfqC6MRxyW",
"type": "text",
"blockId": "2Vrpgk5VP9BUo3vKtM5kws",
"groupId": "2Vrpgk5VP9BUo3vKtM5kws",
"content": {
"html": "<div>Do you eat pizza?</div>",
"richText": [
@@ -97,21 +97,21 @@
{
"id": "wGKXMr4mfySw1HNThND2Xd",
"type": 0,
"stepId": "sxn8UjQ2MjEMuRjhkh7LWws",
"blockId": "sxn8UjQ2MjEMuRjhkh7LWws",
"content": "Yes"
},
{
"id": "qzqzVqMeo6TDUdMYckLZmf",
"type": 0,
"stepId": "sxn8UjQ2MjEMuRjhkh7LWws",
"blockId": "sxn8UjQ2MjEMuRjhkh7LWws",
"content": "No"
}
],
"blockId": "2Vrpgk5VP9BUo3vKtM5kws",
"groupId": "2Vrpgk5VP9BUo3vKtM5kws",
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
}
],
"title": "Block #1",
"title": "Group #1",
"graphCoordinates": { "x": 386, "y": 108 }
}
],
@@ -119,10 +119,10 @@
"edges": [
{
"id": "b3XsreaqtWt4CrZZmCKDpa",
"to": { "blockId": "2Vrpgk5VP9BUo3vKtM5kws" },
"to": { "groupId": "2Vrpgk5VP9BUo3vKtM5kws" },
"from": {
"stepId": "u7Px8eD9MWXNJEBwxQwJCF",
"blockId": "dqork4dJJZk3RgKYavBpRE"
"blockId": "u7Px8eD9MWXNJEBwxQwJCF",
"groupId": "dqork4dJJZk3RgKYavBpRE"
}
}
],

View File

@@ -5,15 +5,15 @@
"name": "My typebot",
"publishedTypebotId": null,
"folderId": null,
"blocks": [
"groups": [
{
"id": "1qQrnsLzRim1LqCrhbj1MW",
"steps": [
"blocks": [
{
"id": "8srsGhdBJK8v88Xo1RRS4C",
"type": "start",
"label": "Start",
"blockId": "1qQrnsLzRim1LqCrhbj1MW",
"groupId": "1qQrnsLzRim1LqCrhbj1MW",
"outgoingEdgeId": "ovUHhwr6THMhqtn8QbkjtA"
}
],
@@ -22,15 +22,15 @@
},
{
"id": "wSR4VCcDNDTTsD9Szi2xH8",
"steps": [
"blocks": [
{
"id": "sw6nHJfkMsM4pxZxMBB6QqW",
"type": "Typebot link",
"blockId": "wSR4VCcDNDTTsD9Szi2xH8",
"groupId": "wSR4VCcDNDTTsD9Szi2xH8",
"options": { "typebotId": "cl0ibhv8d0130n21aw8doxhj5" }
}
],
"title": "Block #1",
"title": "Group #1",
"graphCoordinates": { "x": 363, "y": 199 }
}
],
@@ -38,10 +38,10 @@
"edges": [
{
"id": "ovUHhwr6THMhqtn8QbkjtA",
"to": { "blockId": "wSR4VCcDNDTTsD9Szi2xH8" },
"to": { "groupId": "wSR4VCcDNDTTsD9Szi2xH8" },
"from": {
"stepId": "8srsGhdBJK8v88Xo1RRS4C",
"blockId": "1qQrnsLzRim1LqCrhbj1MW"
"blockId": "8srsGhdBJK8v88Xo1RRS4C",
"groupId": "1qQrnsLzRim1LqCrhbj1MW"
}
}
],

View File

@@ -5,15 +5,15 @@
"name": "Another typebot",
"publishedTypebotId": null,
"folderId": null,
"blocks": [
"groups": [
{
"id": "p4ByLVoKiDRyRoPHKmcTfw",
"steps": [
"blocks": [
{
"id": "rw6smEWEJzHKbiVKLUKFvZ",
"type": "start",
"label": "Start",
"blockId": "p4ByLVoKiDRyRoPHKmcTfw",
"groupId": "p4ByLVoKiDRyRoPHKmcTfw",
"outgoingEdgeId": "1z3pfiatTUHbraD2uSoA3E"
}
],
@@ -23,11 +23,11 @@
{
"id": "bg4QEJseUsTP496H27j5k2",
"graphCoordinates": { "x": 366, "y": 191 },
"title": "Block #1",
"steps": [
"title": "Group #1",
"blocks": [
{
"id": "s8ZeBL9p5za77eBmdKECLYq",
"blockId": "bg4QEJseUsTP496H27j5k2",
"groupId": "bg4QEJseUsTP496H27j5k2",
"type": "text input",
"options": {
"isLong": false,
@@ -41,10 +41,10 @@
"edges": [
{
"from": {
"blockId": "p4ByLVoKiDRyRoPHKmcTfw",
"stepId": "rw6smEWEJzHKbiVKLUKFvZ"
"groupId": "p4ByLVoKiDRyRoPHKmcTfw",
"blockId": "rw6smEWEJzHKbiVKLUKFvZ"
},
"to": { "blockId": "bg4QEJseUsTP496H27j5k2" },
"to": { "groupId": "bg4QEJseUsTP496H27j5k2" },
"id": "1z3pfiatTUHbraD2uSoA3E"
}
],

View File

@@ -5,15 +5,15 @@
"name": "My typebot",
"publishedTypebotId": null,
"folderId": null,
"blocks": [
"groups": [
{
"id": "cl13bgvlk0000t71a4wabccvw",
"steps": [
"blocks": [
{
"id": "cl13bgvlk0001t71a3pilbj53",
"type": "start",
"label": "Start",
"blockId": "cl13bgvlk0000t71a4wabccvw",
"groupId": "cl13bgvlk0000t71a4wabccvw",
"outgoingEdgeId": "cl13bgz4800062e6dv7ejcchb"
}
],
@@ -23,11 +23,11 @@
{
"id": "cl13bgy1s00042e6dao1wyobm",
"graphCoordinates": { "x": 329, "y": 65 },
"title": "Block #1",
"steps": [
"title": "Group #1",
"blocks": [
{
"id": "cl13bgy1w00052e6d5x57wt7o",
"blockId": "cl13bgy1s00042e6dao1wyobm",
"groupId": "cl13bgy1s00042e6dao1wyobm",
"type": "text",
"content": {
"html": "<div>Hey I know you!</div>",
@@ -39,7 +39,7 @@
},
{
"id": "cl13bh6jd00072e6dftdirwy4",
"blockId": "cl13bgy1s00042e6dao1wyobm",
"groupId": "cl13bgy1s00042e6dao1wyobm",
"type": "text",
"content": {
"html": "<div>Your name is {{Name}}</div>",
@@ -51,7 +51,7 @@
},
{
"id": "cl13bhfxd00092e6dydvcqlhm",
"blockId": "cl13bgy1s00042e6dao1wyobm",
"groupId": "cl13bgy1s00042e6dao1wyobm",
"type": "text",
"content": {
"html": "<div>What&#x27;s your email?</div>",
@@ -63,7 +63,7 @@
},
{
"id": "cl13bhnay000a2e6dxa630dh3",
"blockId": "cl13bgy1s00042e6dao1wyobm",
"groupId": "cl13bgy1s00042e6dao1wyobm",
"type": "email input",
"options": {
"labels": { "button": "Send", "placeholder": "Type your email..." },
@@ -81,10 +81,10 @@
"edges": [
{
"from": {
"blockId": "cl13bgvlk0000t71a4wabccvw",
"stepId": "cl13bgvlk0001t71a3pilbj53"
"groupId": "cl13bgvlk0000t71a4wabccvw",
"blockId": "cl13bgvlk0001t71a3pilbj53"
},
"to": { "blockId": "cl13bgy1s00042e6dao1wyobm" },
"to": { "groupId": "cl13bgy1s00042e6dao1wyobm" },
"id": "cl13bgz4800062e6dv7ejcchb"
}
],

View File

@@ -6,15 +6,15 @@
"name": "My typebot",
"publishedTypebotId": null,
"folderId": null,
"blocks": [
"groups": [
{
"id": "cl1rxxg6k000009lhd0mgfy5i",
"steps": [
"blocks": [
{
"id": "cl1rxxg6k000109lh2is0gfua",
"type": "start",
"label": "Start",
"blockId": "cl1rxxg6k000009lhd0mgfy5i",
"groupId": "cl1rxxg6k000009lhd0mgfy5i",
"outgoingEdgeId": "cl1w8rhzs000f2e694836a1k3"
}
],
@@ -25,16 +25,16 @@
"id": "cl1w8repd000b2e69fwiqsd00",
"graphCoordinates": { "x": 364, "y": -2 },
"title": "Group #1",
"steps": [
"blocks": [
{
"id": "cl1w8repg000c2e699jqwrepg",
"blockId": "cl1w8repd000b2e69fwiqsd00",
"groupId": "cl1w8repd000b2e69fwiqsd00",
"type": "choice input",
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
"items": [
{
"id": "cl1w8repg000d2e69d8xnkqeq",
"stepId": "cl1w8repg000c2e699jqwrepg",
"blockId": "cl1w8repg000c2e699jqwrepg",
"type": 0,
"content": "Send email",
"outgoingEdgeId": "cl1w8rkoo000i2e69hs60pk0q"
@@ -47,10 +47,10 @@
"id": "cl1w8rjaf000g2e69cqd2bwvk",
"graphCoordinates": { "x": 715, "y": -10 },
"title": "Group #2",
"steps": [
"blocks": [
{
"id": "cl1w8rjai000h2e695uvoimq7",
"blockId": "cl1w8rjaf000g2e69cqd2bwvk",
"groupId": "cl1w8rjaf000g2e69cqd2bwvk",
"type": "Email",
"options": {
"credentialsId": "send-email-credentials",
@@ -72,19 +72,19 @@
"edges": [
{
"from": {
"blockId": "cl1rxxg6k000009lhd0mgfy5i",
"stepId": "cl1rxxg6k000109lh2is0gfua"
"groupId": "cl1rxxg6k000009lhd0mgfy5i",
"blockId": "cl1rxxg6k000109lh2is0gfua"
},
"to": { "blockId": "cl1w8repd000b2e69fwiqsd00" },
"to": { "groupId": "cl1w8repd000b2e69fwiqsd00" },
"id": "cl1w8rhzs000f2e694836a1k3"
},
{
"from": {
"blockId": "cl1w8repd000b2e69fwiqsd00",
"stepId": "cl1w8repg000c2e699jqwrepg",
"groupId": "cl1w8repd000b2e69fwiqsd00",
"blockId": "cl1w8repg000c2e699jqwrepg",
"itemId": "cl1w8repg000d2e69d8xnkqeq"
},
"to": { "blockId": "cl1w8rjaf000g2e69cqd2bwvk" },
"to": { "groupId": "cl1w8rjaf000g2e69cqd2bwvk" },
"id": "cl1w8rkoo000i2e69hs60pk0q"
}
],

View File

@@ -6,15 +6,15 @@
"name": "My typebot",
"publishedTypebotId": null,
"folderId": null,
"blocks": [
"groups": [
{
"id": "cl26li8fj0000iez05x7razkg",
"steps": [
"blocks": [
{
"id": "cl26li8fj0001iez0bqfraw9h",
"type": "start",
"label": "Start",
"blockId": "cl26li8fj0000iez05x7razkg",
"groupId": "cl26li8fj0000iez05x7razkg",
"outgoingEdgeId": "cl26liqj6000g2e6ed2cwkvse"
}
],
@@ -23,7 +23,7 @@
},
{
"id": "cl26lidjz000a2e6etf4v03hv",
"steps": [
"blocks": [
{
"id": "cl26lidk4000b2e6es2fos0nl",
"type": "choice input",
@@ -31,17 +31,17 @@
{
"id": "cl26lidk5000c2e6e39wyc7wq",
"type": 0,
"stepId": "cl26lidk4000b2e6es2fos0nl",
"blockId": "cl26lidk4000b2e6es2fos0nl",
"content": "Send success webhook"
}
],
"blockId": "cl26lidjz000a2e6etf4v03hv",
"groupId": "cl26lidjz000a2e6etf4v03hv",
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
},
{
"id": "cl26lip76000e2e6ebmph843a",
"type": "Webhook",
"blockId": "cl26lidjz000a2e6etf4v03hv",
"groupId": "cl26lidjz000a2e6etf4v03hv",
"options": {
"isCustomBody": false,
"isAdvancedConfig": false,
@@ -52,13 +52,13 @@
},
{
"id": "cl26m0pdz00042e6ebjdoclaa",
"blockId": "cl26lidjz000a2e6etf4v03hv",
"groupId": "cl26lidjz000a2e6etf4v03hv",
"type": "choice input",
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
"items": [
{
"id": "cl26m0pdz00052e6ecmxwfz44",
"stepId": "cl26m0pdz00042e6ebjdoclaa",
"blockId": "cl26m0pdz00042e6ebjdoclaa",
"type": 0,
"content": "Send failed webhook"
}
@@ -66,7 +66,7 @@
},
{
"id": "cl26m0w9b00072e6eld1ei291",
"blockId": "cl26lidjz000a2e6etf4v03hv",
"groupId": "cl26lidjz000a2e6etf4v03hv",
"type": "Webhook",
"options": {
"responseVariableMapping": [],
@@ -89,10 +89,10 @@
"edges": [
{
"id": "cl26liqj6000g2e6ed2cwkvse",
"to": { "blockId": "cl26lidjz000a2e6etf4v03hv" },
"to": { "groupId": "cl26lidjz000a2e6etf4v03hv" },
"from": {
"stepId": "cl26li8fj0001iez0bqfraw9h",
"blockId": "cl26li8fj0000iez05x7razkg"
"blockId": "cl26li8fj0001iez0bqfraw9h",
"groupId": "cl26li8fj0000iez05x7razkg"
}
}
],

View File

@@ -4,7 +4,7 @@ import {
defaultTheme,
PublicTypebot,
SmtpCredentialsData,
Step,
Block,
Typebot,
Webhook,
} from 'models'
@@ -92,7 +92,7 @@ const parseTypebotToPublicTypebot = (
typebot: Typebot
): PublicTypebot => ({
id,
blocks: typebot.blocks,
groups: typebot.groups,
typebotId: typebot.id,
theme: typebot.theme,
settings: typebot.settings,
@@ -120,44 +120,44 @@ const parseTestTypebot = (partialTypebot: Partial<Typebot>): Typebot => ({
edges: [
{
id: 'edge1',
from: { blockId: 'block0', stepId: 'step0' },
to: { blockId: 'block1' },
from: { groupId: 'group0', blockId: 'block0' },
to: { groupId: 'group1' },
},
],
blocks: [
groups: [
{
id: 'block0',
title: 'Block #0',
steps: [
id: 'group0',
title: 'Group #0',
blocks: [
{
id: 'step0',
id: 'block0',
type: 'start',
blockId: 'block0',
groupId: 'group0',
label: 'Start',
outgoingEdgeId: 'edge1',
},
],
graphCoordinates: { x: 0, y: 0 },
},
...(partialTypebot.blocks ?? []),
...(partialTypebot.groups ?? []),
],
})
export const parseDefaultBlockWithStep = (
step: Partial<Step>
): Pick<Typebot, 'blocks'> => ({
blocks: [
export const parseDefaultGroupWithBlock = (
block: Partial<Block>
): Pick<Typebot, 'groups'> => ({
groups: [
{
graphCoordinates: { x: 200, y: 200 },
id: 'block1',
steps: [
id: 'group1',
blocks: [
{
id: 'step1',
blockId: 'block1',
...step,
} as Step,
id: 'block1',
groupId: 'group1',
...block,
} as Block,
],
title: 'Block #1',
title: 'Group #1',
},
],
})
@@ -209,8 +209,8 @@ const createAnswers = () => {
...Array.from(Array(200)).map((_, idx) => ({
resultId: `result${idx}`,
content: `content${idx}`,
stepId: 'step1',
blockId: 'block1',
groupId: 'group1',
})),
],
})

View File

@@ -34,7 +34,7 @@ test('can list typebots', async ({ request }) => {
})
})
test('can get webhook steps', async ({ request }) => {
test('can get webhook blocks', async ({ request }) => {
expect(
(await request.get(`/api/typebots/${typebotId}/webhookBlocks`)).status()
).toBe(401)
@@ -47,8 +47,8 @@ test('can get webhook steps', async ({ request }) => {
const { blocks } = await response.json()
expect(blocks).toHaveLength(1)
expect(blocks[0]).toEqual({
blockId: 'webhookStep',
name: 'Webhook > webhookStep',
blockId: 'webhookBlock',
name: 'Webhook > webhookBlock',
})
})
@@ -56,13 +56,13 @@ test('can subscribe webhook', async ({ request }) => {
expect(
(
await request.post(
`/api/typebots/${typebotId}/blocks/webhookStep/subscribeWebhook`,
`/api/typebots/${typebotId}/blocks/webhookBlock/subscribeWebhook`,
{ data: { url: 'https://test.com' } }
)
).status()
).toBe(401)
const response = await request.post(
`/api/typebots/${typebotId}/blocks/webhookStep/subscribeWebhook`,
`/api/typebots/${typebotId}/blocks/webhookBlock/subscribeWebhook`,
{
headers: {
Authorization: 'Bearer userToken',
@@ -80,12 +80,12 @@ test('can unsubscribe webhook', async ({ request }) => {
expect(
(
await request.post(
`/api/typebots/${typebotId}/blocks/webhookStep/unsubscribeWebhook`
`/api/typebots/${typebotId}/blocks/webhookBlock/unsubscribeWebhook`
)
).status()
).toBe(401)
const response = await request.post(
`/api/typebots/${typebotId}/blocks/webhookStep/unsubscribeWebhook`,
`/api/typebots/${typebotId}/blocks/webhookBlock/unsubscribeWebhook`,
{
headers: { Authorization: 'Bearer userToken' },
}
@@ -100,12 +100,12 @@ test('can get a sample result', async ({ request }) => {
expect(
(
await request.get(
`/api/typebots/${typebotId}/blocks/webhookStep/sampleResult`
`/api/typebots/${typebotId}/blocks/webhookBlock/sampleResult`
)
).status()
).toBe(401)
const response = await request.get(
`/api/typebots/${typebotId}/blocks/webhookStep/sampleResult`,
`/api/typebots/${typebotId}/blocks/webhookBlock/sampleResult`,
{
headers: { Authorization: 'Bearer userToken' },
}

View File

@@ -7,7 +7,7 @@ import cuid from 'cuid'
test('should work as expected', async ({ page }) => {
const typebotId = cuid()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/hugeBlock.json'),
path.join(__dirname, '../fixtures/typebots/hugeGroup.json'),
{ id: typebotId, publicId: `${typebotId}-public` }
)
await page.goto(`/${typebotId}-public`)

View File

@@ -1,9 +1,12 @@
import test, { expect } from '@playwright/test'
import { createTypebots, parseDefaultBlockWithStep } from '../services/database'
import {
createTypebots,
parseDefaultGroupWithBlock,
} from '../services/database'
import {
defaultSettings,
defaultTextInputOptions,
InputStepType,
InputBlockType,
Metadata,
} from 'models'
import { typebotViewer } from '../services/selectorUtils'
@@ -24,8 +27,8 @@ test('Should correctly parse metadata', async ({ page }) => {
...defaultSettings,
metadata: customMetadata,
},
...parseDefaultBlockWithStep({
type: InputStepType.TEXT,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
options: defaultTextInputOptions,
}),
},

View File

@@ -1,19 +1,23 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
parseDefaultGroupWithBlock,
updateTypebot,
} from '../services/database'
import cuid from 'cuid'
import { defaultSettings, defaultTextInputOptions, InputStepType } from 'models'
import {
defaultSettings,
defaultTextInputOptions,
InputBlockType,
} from 'models'
test('Result should be in storage by default', async ({ page }) => {
const typebotId = cuid()
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.TEXT,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
options: defaultTextInputOptions,
}),
},
@@ -45,8 +49,8 @@ test.describe('Create result on page refresh enabled', () => {
isNewResultOnRefreshEnabled: true,
},
},
...parseDefaultBlockWithStep({
type: InputStepType.TEXT,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
options: defaultTextInputOptions,
}),
},
@@ -81,8 +85,8 @@ test('Hide query params', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.TEXT,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
options: defaultTextInputOptions,
}),
},

View File

@@ -1,6 +1,11 @@
import { User } from 'db'
import prisma from 'libs/prisma'
import { LogicStepType, Typebot, TypebotLinkStep, PublicTypebot } from 'models'
import {
LogicBlockType,
Typebot,
TypebotLinkBlock,
PublicTypebot,
} from 'models'
import { NextApiRequest } from 'next'
import { isDefined } from 'utils'
import { canReadTypebots } from './dbRules'
@@ -63,13 +68,13 @@ export const getLinkedTypebots = async (
user?: User
): Promise<(Typebot | PublicTypebot)[]> => {
const linkedTypebotIds = (
typebot.blocks
.flatMap((b) => b.steps)
typebot.groups
.flatMap((g) => g.blocks)
.filter(
(s) =>
s.type === LogicStepType.TYPEBOT_LINK &&
s.type === LogicBlockType.TYPEBOT_LINK &&
isDefined(s.options.typebotId)
) as TypebotLinkStep[]
) as TypebotLinkBlock[]
).map((s) => s.options.typebotId as string)
if (linkedTypebotIds.length === 0) return []
const typebots = (await ('typebotId' in typebot

View File

@@ -1,71 +1,71 @@
import {
InputStep,
InputStepType,
LogicStepType,
InputBlock,
InputBlockType,
LogicBlockType,
PublicTypebot,
ResultHeaderCell,
Step,
Block,
Typebot,
TypebotLinkStep,
TypebotLinkBlock,
} from 'models'
import { isInputStep, byId, parseResultHeader, isNotDefined } from 'utils'
import { isInputBlock, byId, parseResultHeader, isNotDefined } from 'utils'
export const parseSampleResult =
(
typebot: Pick<Typebot | PublicTypebot, 'blocks' | 'variables' | 'edges'>,
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>,
linkedTypebots: (Typebot | PublicTypebot)[]
) =>
async (
currentBlockId: string
currentGroupId: string
): Promise<Record<string, string | boolean | undefined>> => {
const header = parseResultHeader({
blocks: [...typebot.blocks, ...linkedTypebots.flatMap((t) => t.blocks)],
groups: [...typebot.groups, ...linkedTypebots.flatMap((t) => t.groups)],
variables: [
...typebot.variables,
...linkedTypebots.flatMap((t) => t.variables),
],
})
const linkedInputSteps = await extractLinkedInputSteps(
const linkedInputBlocks = await extractLinkedInputBlocks(
typebot,
linkedTypebots
)(currentBlockId)
)(currentGroupId)
return {
message: 'This is a sample result, it has been generated ⬇️',
'Submitted at': new Date().toISOString(),
...parseBlocksResultSample(linkedInputSteps, header),
...parseGroupsResultSample(linkedInputBlocks, header),
}
}
const extractLinkedInputSteps =
const extractLinkedInputBlocks =
(
typebot: Pick<Typebot | PublicTypebot, 'blocks' | 'variables' | 'edges'>,
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>,
linkedTypebots: (Typebot | PublicTypebot)[]
) =>
async (
currentBlockId?: string,
currentGroupId?: string,
direction: 'backward' | 'forward' = 'backward'
): Promise<InputStep[]> => {
const previousLinkedTypebotSteps = walkEdgesAndExtract(
): Promise<InputBlock[]> => {
const previousLinkedTypebotBlocks = walkEdgesAndExtract(
'linkedBot',
direction,
typebot
)({
blockId: currentBlockId,
}) as TypebotLinkStep[]
groupId: currentGroupId,
}) as TypebotLinkBlock[]
const linkedBotInputs =
previousLinkedTypebotSteps.length > 0
previousLinkedTypebotBlocks.length > 0
? await Promise.all(
previousLinkedTypebotSteps.map((linkedBot) =>
extractLinkedInputSteps(
previousLinkedTypebotBlocks.map((linkedBot) =>
extractLinkedInputBlocks(
linkedTypebots.find((t) =>
'typebotId' in t
? t.typebotId === linkedBot.options.typebotId
: t.id === linkedBot.options.typebotId
) as Typebot | PublicTypebot,
linkedTypebots
)(linkedBot.options.blockId, 'forward')
)(linkedBot.options.groupId, 'forward')
)
)
: []
@@ -76,49 +76,52 @@ const extractLinkedInputSteps =
direction,
typebot
)({
blockId: currentBlockId,
}) as InputStep[]
groupId: currentGroupId,
}) as InputBlock[]
).concat(linkedBotInputs.flatMap((l) => l))
}
const parseBlocksResultSample = (
inputSteps: InputStep[],
const parseGroupsResultSample = (
inputBlocks: InputBlock[],
header: ResultHeaderCell[]
) =>
header.reduce<Record<string, string | boolean | undefined>>((steps, cell) => {
const inputStep = inputSteps.find((step) => step.id === cell.stepId)
if (isNotDefined(inputStep)) {
if (cell.variableId)
return {
...steps,
[cell.label]: 'content',
}
return steps
}
const value = getSampleValue(inputStep)
return {
...steps,
[cell.label]: value,
}
}, {})
header.reduce<Record<string, string | boolean | undefined>>(
(blocks, cell) => {
const inputBlock = inputBlocks.find((block) => block.id === cell.blockId)
if (isNotDefined(inputBlock)) {
if (cell.variableId)
return {
...blocks,
[cell.label]: 'content',
}
return blocks
}
const value = getSampleValue(inputBlock)
return {
...blocks,
[cell.label]: value,
}
},
{}
)
const getSampleValue = (step: InputStep) => {
switch (step.type) {
case InputStepType.CHOICE:
return step.options.isMultipleChoice
? step.items.map((i) => i.content).join(', ')
: step.items[0]?.content ?? 'Item'
case InputStepType.DATE:
const getSampleValue = (block: InputBlock) => {
switch (block.type) {
case InputBlockType.CHOICE:
return block.options.isMultipleChoice
? block.items.map((i) => i.content).join(', ')
: block.items[0]?.content ?? 'Item'
case InputBlockType.DATE:
return new Date().toUTCString()
case InputStepType.EMAIL:
case InputBlockType.EMAIL:
return 'test@email.com'
case InputStepType.NUMBER:
case InputBlockType.NUMBER:
return '20'
case InputStepType.PHONE:
case InputBlockType.PHONE:
return '+33665566773'
case InputStepType.TEXT:
case InputBlockType.TEXT:
return 'answer value'
case InputStepType.URL:
case InputBlockType.URL:
return 'https://test.com'
}
}
@@ -127,67 +130,67 @@ const walkEdgesAndExtract =
(
type: 'input' | 'linkedBot',
direction: 'backward' | 'forward',
typebot: Pick<Typebot | PublicTypebot, 'blocks' | 'variables' | 'edges'>
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>
) =>
({ blockId }: { blockId?: string }): Step[] => {
const currentBlockId =
blockId ??
(typebot.blocks.find((b) => b.steps[0].type === 'start')?.id as string)
const stepsInBlock = extractStepsInBlock(
({ groupId }: { groupId?: string }): Block[] => {
const currentGroupId =
groupId ??
(typebot.groups.find((b) => b.blocks[0].type === 'start')?.id as string)
const blocksInGroup = extractBlocksInGroup(
type,
typebot
)({
blockId: currentBlockId,
groupId: currentGroupId,
})
const otherBlockIds = getBlockIds(typebot, direction)(currentBlockId)
const otherGroupIds = getGroupIds(typebot, direction)(currentGroupId)
return [
...stepsInBlock,
...otherBlockIds.flatMap((blockId) =>
extractStepsInBlock(type, typebot)({ blockId })
...blocksInGroup,
...otherGroupIds.flatMap((groupId) =>
extractBlocksInGroup(type, typebot)({ groupId })
),
]
}
const getBlockIds =
const getGroupIds =
(
typebot: Pick<Typebot | PublicTypebot, 'blocks' | 'variables' | 'edges'>,
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>,
direction: 'backward' | 'forward',
existingBlockIds?: string[]
existingGroupIds?: string[]
) =>
(blockId: string): string[] => {
const blocks = typebot.edges.reduce<string[]>((blockIds, edge) => {
(groupId: string): string[] => {
const groups = typebot.edges.reduce<string[]>((groupIds, edge) => {
if (direction === 'forward')
return (!existingBlockIds ||
!existingBlockIds?.includes(edge.to.blockId)) &&
edge.from.blockId === blockId
? [...blockIds, edge.to.blockId]
: blockIds
return (!existingBlockIds ||
!existingBlockIds.includes(edge.from.blockId)) &&
edge.to.blockId === blockId
? [...blockIds, edge.from.blockId]
: blockIds
return (!existingGroupIds ||
!existingGroupIds?.includes(edge.to.groupId)) &&
edge.from.groupId === groupId
? [...groupIds, edge.to.groupId]
: groupIds
return (!existingGroupIds ||
!existingGroupIds.includes(edge.from.groupId)) &&
edge.to.groupId === groupId
? [...groupIds, edge.from.groupId]
: groupIds
}, [])
const newBlocks = [...(existingBlockIds ?? []), ...blocks]
return blocks.concat(
blocks.flatMap(getBlockIds(typebot, direction, newBlocks))
const newGroups = [...(existingGroupIds ?? []), ...groups]
return groups.concat(
groups.flatMap(getGroupIds(typebot, direction, newGroups))
)
}
const extractStepsInBlock =
const extractBlocksInGroup =
(
type: 'input' | 'linkedBot',
typebot: Pick<Typebot | PublicTypebot, 'blocks' | 'variables' | 'edges'>
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>
) =>
({ blockId, stepId }: { blockId: string; stepId?: string }) => {
const currentBlock = typebot.blocks.find(byId(blockId))
if (!currentBlock) return []
const steps: Step[] = []
for (const step of currentBlock.steps) {
if (step.id === stepId) break
if (type === 'input' && isInputStep(step)) steps.push(step)
if (type === 'linkedBot' && step.type === LogicStepType.TYPEBOT_LINK)
steps.push(step)
({ groupId, blockId }: { groupId: string; blockId?: string }) => {
const currentGroup = typebot.groups.find(byId(groupId))
if (!currentGroup) return []
const blocks: Block[] = []
for (const block of currentGroup.blocks) {
if (block.id === blockId) break
if (type === 'input' && isInputBlock(block)) blocks.push(block)
if (type === 'linkedBot' && block.type === LogicBlockType.TYPEBOT_LINK)
blocks.push(block)
}
return steps
return blocks
}