2
0

🐛 (webhook) Update zapier and make.com result sample parser

This commit is contained in:
Baptiste Arnaud
2024-01-22 13:57:04 +01:00
parent 42008f8c18
commit 233e99301e
2 changed files with 10 additions and 273 deletions

View File

@ -2,10 +2,11 @@ import prisma from '@typebot.io/lib/prisma'
import { canReadTypebots } from '@/helpers/databaseRules'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
import { Block, Typebot } 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'
import { parseSampleResult } from '@typebot.io/bot-engine/blocks/integrations/webhook/parseSampleResult'
import { getBlockById } from '@typebot.io/lib/getBlockById'
export const getResultExample = authenticatedProcedure
.meta({
@ -27,15 +28,7 @@ export const getResultExample = authenticatedProcedure
)
.output(
z.object({
resultExample: z
.object({
message: z.literal(
'This is a sample result, it has been generated ⬇️'
),
'Submitted at': z.string(),
})
.and(z.record(z.string().optional()))
.describe('Can contain any fields.'),
resultExample: z.record(z.any()).describe('Can contain any fields.'),
})
)
.query(async ({ input: { typebotId, blockId }, ctx: { user } }) => {
@ -52,20 +45,17 @@ export const getResultExample = authenticatedProcedure
if (!typebot)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
const block = typebot.groups
.flatMap<Block>((group) => group.blocks)
.find((block) => block.id === blockId)
const { group } = getBlockById(blockId, typebot.groups)
if (!block)
if (!group)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Block not found' })
const linkedTypebots = await fetchLinkedTypebots(typebot, user)
return {
resultExample: await parseResultExample({
typebot,
linkedTypebots,
userEmail: user.email ?? 'test@email.com',
})(block.id),
resultExample: await parseSampleResult(typebot, linkedTypebots)(
group.id,
typebot.variables
),
}
})

View File

@ -1,253 +0,0 @@
import {
InputBlock,
PublicTypebot,
ResultHeaderCell,
Block,
Typebot,
TypebotLinkBlock,
} from '@typebot.io/schemas'
import { isInputBlock, byId, isNotDefined } from '@typebot.io/lib'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
import { EventType } from '@typebot.io/schemas/features/events/constants'
import { parseResultHeader } from '@typebot.io/lib/results/parseResultHeader'
export const parseResultExample =
({
typebot,
linkedTypebots,
userEmail,
}: {
typebot: Pick<
Typebot | PublicTypebot,
'groups' | 'variables' | 'edges' | 'events'
>
linkedTypebots: (Typebot | PublicTypebot)[]
userEmail: string
}) =>
async (
currentBlockId: string
): Promise<
{
message: 'This is a sample result, it has been generated ⬇️'
'Submitted at': string
} & { [k: string]: string | undefined }
> => {
const header = parseResultHeader(typebot, linkedTypebots)
const linkedInputBlocks = await extractLinkedInputBlocks(
typebot,
linkedTypebots
)(currentBlockId)
return {
message: 'This is a sample result, it has been generated ⬇️',
'Submitted at': new Date().toISOString(),
...parseResultSample({
inputBlocks: linkedInputBlocks,
headerCells: header,
userEmail,
}),
}
}
const extractLinkedInputBlocks =
(
typebot:
| Pick<
Typebot | PublicTypebot,
'groups' | 'variables' | 'edges' | 'events'
>
| undefined,
linkedTypebots: (Typebot | PublicTypebot)[]
) =>
async (
blockId?: string,
direction: 'backward' | 'forward' = 'backward'
): Promise<InputBlock[]> => {
if (!typebot) return []
const previousLinkedTypebotBlocks = walkEdgesAndExtract(
'linkedBot',
direction,
typebot
)({
blockId,
}) as TypebotLinkBlock[]
const linkedBotInputs =
previousLinkedTypebotBlocks.length > 0
? await Promise.all(
previousLinkedTypebotBlocks.map((linkedBot) => {
const typebot = linkedTypebots.find((t) =>
'typebotId' in t
? t.typebotId === linkedBot.options?.typebotId
: t.id === linkedBot.options?.typebotId
)
const blockId = linkedBot.options?.groupId
? typebot?.groups
.find(byId(linkedBot.options?.groupId))
?.blocks.at(0)?.id
: undefined
return extractLinkedInputBlocks(typebot, linkedTypebots)(
blockId,
'forward'
)
})
)
: []
return (
walkEdgesAndExtract(
'input',
direction,
typebot
)({
blockId,
}) as InputBlock[]
).concat(linkedBotInputs.flatMap((l) => l))
}
const parseResultSample = ({
inputBlocks,
headerCells,
userEmail,
}: {
inputBlocks: InputBlock[]
headerCells: ResultHeaderCell[]
userEmail: string
}) =>
headerCells.reduce<Record<string, string | undefined>>(
(resultSample, cell) => {
const inputBlock = inputBlocks.find((inputBlock) =>
cell.blocks?.some((block) => block.id === inputBlock.id)
)
if (isNotDefined(inputBlock)) {
if (cell.variableIds)
return {
...resultSample,
[cell.label]: 'content',
}
return resultSample
}
const value = getSampleValue({ block: inputBlock, userEmail })
return {
...resultSample,
[cell.label]: value,
}
},
{}
)
const getSampleValue = ({
block,
userEmail,
}: {
block: InputBlock
userEmail: string
}) => {
switch (block.type) {
case InputBlockType.CHOICE:
return block.options?.isMultipleChoice
? block.items?.map((i) => i.content).join(', ')
: block.items?.at(0)?.content ?? 'Item'
case InputBlockType.DATE:
return new Date().toUTCString()
case InputBlockType.EMAIL:
return userEmail
case InputBlockType.NUMBER:
return '20'
case InputBlockType.PHONE:
return '+33665566773'
case InputBlockType.TEXT:
return 'answer value'
case InputBlockType.URL:
return 'https://test.com'
}
}
const walkEdgesAndExtract =
(
type: 'input' | 'linkedBot',
direction: 'backward' | 'forward',
typebot: Pick<
Typebot | PublicTypebot,
'groups' | 'variables' | 'edges' | 'events'
>
) =>
({ blockId }: { blockId?: string }): Block[] => {
const groupId = typebot.groups.find((g) =>
g.blocks.some((b) => b.id === blockId)
)?.id
const startEventEdgeId = groupId
? undefined
: typebot.events?.find((e) => e.type === EventType.START)?.outgoingEdgeId
const currentGroupId =
groupId ??
(startEventEdgeId
? typebot.edges.find(byId(startEventEdgeId))?.to.groupId
: typebot.groups.find((g) => g.blocks[0].type === 'start')?.id)
if (!currentGroupId)
throw new Error("walkEdgesAndExtract - Can't find currentGroupId")
const blocksInGroup = extractBlocksInGroup(
type,
typebot
)({
groupId: currentGroupId,
blockId,
})
const otherGroupIds = getConnectedGroups(typebot, direction)(currentGroupId)
return [
...blocksInGroup,
...otherGroupIds.flatMap((groupId) =>
extractBlocksInGroup(type, typebot)({ groupId, blockId })
),
]
}
const getConnectedGroups =
(
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>,
direction: 'backward' | 'forward',
existingGroupIds?: string[]
) =>
(groupId: string): string[] => {
const groups = typebot.edges.reduce<string[]>((groupIds, edge) => {
const fromGroupId = typebot.groups.find((g) =>
g.blocks.some(
(b) => 'blockId' in edge.from && b.id === edge.from.blockId
)
)?.id
if (!fromGroupId) return groupIds
if (direction === 'forward')
return (!existingGroupIds ||
!existingGroupIds?.includes(edge.to.groupId)) &&
fromGroupId === groupId
? [...groupIds, edge.to.groupId]
: groupIds
return (!existingGroupIds || !existingGroupIds.includes(fromGroupId)) &&
edge.to.groupId === groupId
? [...groupIds, fromGroupId]
: groupIds
}, [])
const newGroups = [...(existingGroupIds ?? []), ...groups]
return groups.concat(
groups.flatMap(getConnectedGroups(typebot, direction, newGroups))
)
}
const extractBlocksInGroup =
(
type: 'input' | 'linkedBot',
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>
) =>
({ groupId, blockId }: { groupId: string; blockId: string | undefined }) => {
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 blocks
}