import { InputBlock, InputBlockType, LogicBlockType, PublicTypebot, ResultHeaderCell, Block, Typebot, TypebotLinkBlock, Variable, } from '@typebot.io/schemas' import { isInputBlock, byId, isNotDefined } from '@typebot.io/lib' import { parseResultHeader } from '@typebot.io/lib/results' export const parseSampleResult = ( typebot: Pick, linkedTypebots: (Typebot | PublicTypebot)[] ) => async ( currentGroupId: string, variables: Variable[] ): Promise> => { const header = parseResultHeader(typebot, linkedTypebots) const linkedInputBlocks = await extractLinkedInputBlocks( typebot, linkedTypebots )(currentGroupId) return { message: 'This is a sample result, it has been generated ⬇️', submittedAt: new Date().toISOString(), ...parseResultSample(linkedInputBlocks, header, variables), } } const extractLinkedInputBlocks = ( typebot: Pick, linkedTypebots: (Typebot | PublicTypebot)[] ) => async ( currentGroupId?: string, direction: 'backward' | 'forward' = 'backward' ): Promise => { const previousLinkedTypebotBlocks = walkEdgesAndExtract( 'linkedBot', direction, typebot )({ groupId: currentGroupId, }) as TypebotLinkBlock[] const linkedBotInputs = previousLinkedTypebotBlocks.length > 0 ? await Promise.all( 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.groupId, 'forward') ) ) : [] return ( walkEdgesAndExtract( 'input', direction, typebot )({ groupId: currentGroupId, }) as InputBlock[] ).concat(linkedBotInputs.flatMap((l) => l)) } const parseResultSample = ( inputBlocks: InputBlock[], headerCells: ResultHeaderCell[], variables: Variable[] ) => headerCells.reduce>( (resultSample, cell) => { const inputBlock = inputBlocks.find((inputBlock) => cell.blocks?.some((block) => block.id === inputBlock.id) ) if (isNotDefined(inputBlock)) { if (cell.variableIds) { const variableValue = variables.find( (variable) => cell.variableIds?.includes(variable.id) && variable.value )?.value return { ...resultSample, [cell.label]: variableValue ?? 'content', } } return resultSample } const variableValue = variables.find( (variable) => cell.variableIds?.includes(variable.id) && variable.value )?.value const value = variableValue ?? getSampleValue(inputBlock) return { ...resultSample, [cell.label]: value, } }, {} ) 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 InputBlockType.EMAIL: return 'test@email.com' 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 ) => ({ groupId }: { groupId?: string }): Block[] => { const currentGroupId = groupId ?? (typebot.groups.find((b) => b.blocks[0].type === 'start')?.id as string) const blocksInGroup = extractBlocksInGroup( type, typebot )({ groupId: currentGroupId, }) const otherGroupIds = getGroupIds(typebot, direction)(currentGroupId) return [ ...blocksInGroup, ...otherGroupIds.flatMap((groupId) => extractBlocksInGroup(type, typebot)({ groupId }) ), ] } const getGroupIds = ( typebot: Pick, direction: 'backward' | 'forward', existingGroupIds?: string[] ) => (groupId: string): string[] => { const groups = typebot.edges.reduce((groupIds, edge) => { if (direction === 'forward') 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 newGroups = [...(existingGroupIds ?? []), ...groups] return groups.concat( groups.flatMap(getGroupIds(typebot, direction, newGroups)) ) } const extractBlocksInGroup = ( type: 'input' | 'linkedBot', typebot: Pick ) => ({ 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 blocks }