diff --git a/apps/builder/src/features/blocks/integrations/webhook/api/getResultExample.ts b/apps/builder/src/features/blocks/integrations/webhook/api/getResultExample.ts index 18ab165c3..1f09745e9 100644 --- a/apps/builder/src/features/blocks/integrations/webhook/api/getResultExample.ts +++ b/apps/builder/src/features/blocks/integrations/webhook/api/getResultExample.ts @@ -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((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 + ), } }) diff --git a/apps/builder/src/features/blocks/integrations/webhook/helpers/parseResultExample.ts b/apps/builder/src/features/blocks/integrations/webhook/helpers/parseResultExample.ts deleted file mode 100644 index cd6e5c359..000000000 --- a/apps/builder/src/features/blocks/integrations/webhook/helpers/parseResultExample.ts +++ /dev/null @@ -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 => { - 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>( - (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, - direction: 'backward' | 'forward', - existingGroupIds?: string[] - ) => - (groupId: string): string[] => { - const groups = typebot.edges.reduce((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 - ) => - ({ 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 - }