🐛 (webhook) Update zapier and make.com result sample parser
This commit is contained in:
@ -2,10 +2,11 @@ import prisma from '@typebot.io/lib/prisma'
|
|||||||
import { canReadTypebots } from '@/helpers/databaseRules'
|
import { canReadTypebots } from '@/helpers/databaseRules'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { Block, Typebot } from '@typebot.io/schemas'
|
import { Typebot } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { fetchLinkedTypebots } from '@/features/blocks/logic/typebotLink/helpers/fetchLinkedTypebots'
|
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
|
export const getResultExample = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -27,15 +28,7 @@ export const getResultExample = authenticatedProcedure
|
|||||||
)
|
)
|
||||||
.output(
|
.output(
|
||||||
z.object({
|
z.object({
|
||||||
resultExample: z
|
resultExample: z.record(z.any()).describe('Can contain any fields.'),
|
||||||
.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.'),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { typebotId, blockId }, ctx: { user } }) => {
|
.query(async ({ input: { typebotId, blockId }, ctx: { user } }) => {
|
||||||
@ -52,20 +45,17 @@ export const getResultExample = authenticatedProcedure
|
|||||||
if (!typebot)
|
if (!typebot)
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
||||||
|
|
||||||
const block = typebot.groups
|
const { group } = getBlockById(blockId, typebot.groups)
|
||||||
.flatMap<Block>((group) => group.blocks)
|
|
||||||
.find((block) => block.id === blockId)
|
|
||||||
|
|
||||||
if (!block)
|
if (!group)
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Block not found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Block not found' })
|
||||||
|
|
||||||
const linkedTypebots = await fetchLinkedTypebots(typebot, user)
|
const linkedTypebots = await fetchLinkedTypebots(typebot, user)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
resultExample: await parseResultExample({
|
resultExample: await parseSampleResult(typebot, linkedTypebots)(
|
||||||
typebot,
|
group.id,
|
||||||
linkedTypebots,
|
typebot.variables
|
||||||
userEmail: user.email ?? 'test@email.com',
|
),
|
||||||
})(block.id),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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
|
|
||||||
}
|
|
Reference in New Issue
Block a user