Skip to content

Commit

Permalink
feat(api): ✨ Add get sample result endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Feb 21, 2022
1 parent 9dfcb30 commit fd822a3
Show file tree
Hide file tree
Showing 4 changed files with 519 additions and 55 deletions.
29 changes: 2 additions & 27 deletions apps/builder/services/results.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ResultWithAnswers, Typebot, VariableWithValue } from 'models'
import { ResultWithAnswers, VariableWithValue } from 'models'
import useSWRInfinite from 'swr/infinite'
import { fetcher } from './utils'
import { stringify } from 'qs'
import { Answer } from 'db'
import { byId, isDefined, sendRequest } from 'utils'
import { isDefined, sendRequest } from 'utils'

const paginationLimit = 50

Expand Down Expand Up @@ -112,28 +112,3 @@ export const convertResultsToTableData = (results?: ResultWithAnswers[]) =>
return { ...o, [variable.id]: variable.value }
}, {}),
}))

export const parseAnswers = (
result: ResultWithAnswers,
{ blocks, variables }: Pick<Typebot, 'blocks' | 'variables'>
) => ({
submittedAt: result.createdAt,
...[...result.answers, ...result.prefilledVariables].reduce<{
[key: string]: string
}>((o, answerOrVariable) => {
if ('blockId' in answerOrVariable) {
const answer = answerOrVariable as Answer
const key = answer.variableId
? variables.find(byId(answer.variableId))?.name
: blocks.find(byId(answer.blockId))?.title
if (!key) return o
return {
...o,
[key]: answer.content,
}
}
const variable = answerOrVariable as VariableWithValue
if (isDefined(o[variable.id])) return o
return { ...o, [variable.id]: variable.value }
}, {}),
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import prisma from 'libs/prisma'
import { Block, InputStep, InputStepType, Typebot } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { byId, isDefined, isInputStep, methodNotAllowed } from 'utils'

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
const user = await authenticateUser(req)
if (!user) return res.status(401).json({ message: 'Not authenticated' })
const typebotId = req.query.typebotId.toString()
const blockId = req.query.blockId.toString()
const typebot = (await prisma.typebot.findUnique({
where: { id_ownerId: { id: typebotId, ownerId: user.id } },
})) as Typebot | undefined
if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
const previousBlockIds = getPreviousBlocks(typebot)(blockId)
const previousBlocks = typebot.blocks.filter((b) =>
previousBlockIds.includes(b.id)
)
return res.send(parseSampleResult(typebot)(previousBlocks))
}
methodNotAllowed(res)
}

const parseSampleResult =
(typebot: Typebot) =>
(blocks: Block[]): Record<string, string> => {
const parsedBlocks = parseBlocksResultSample(typebot, blocks)
return {
message: 'This is a sample result, it has been generated ⬇️',
'Submitted at': new Date().toISOString(),
...parsedBlocks,
...parseVariablesHeaders(typebot, parsedBlocks),
}
}

const parseBlocksResultSample = (typebot: Typebot, blocks: Block[]) =>
blocks
.filter((block) => typebot && block.steps.some((step) => isInputStep(step)))
.reduce<Record<string, string>>((blocks, block) => {
const inputStep = block.steps.find((step) => isInputStep(step))
if (!inputStep || !isInputStep(inputStep)) return blocks
const matchedVariableName =
inputStep.options.variableId &&
typebot.variables.find(byId(inputStep.options.variableId))?.name
const value = getSampleValue(inputStep)
return {
...blocks,
[matchedVariableName ?? block.title]: value,
}
}, {})

const getSampleValue = (step: InputStep) => {
switch (step.type) {
case InputStepType.CHOICE:
return 'Item 1, Item 2, Item3'
case InputStepType.DATE:
return new Date().toUTCString()
case InputStepType.EMAIL:
return 'test@email.com'
case InputStepType.NUMBER:
return '20'
case InputStepType.PHONE:
return '+33665566773'
case InputStepType.TEXT:
return 'answer value'
case InputStepType.URL:
return 'https://test.com'
}
}

const parseVariablesHeaders = (
typebot: Typebot,
parsedBlocks: Record<string, string>
) =>
typebot.variables.reduce<Record<string, string>>((headers, v) => {
if (parsedBlocks[v.name]) return headers
return {
...headers,
[v.name]: 'value',
}
}, {})

const getPreviousBlocks =
(typebot: Typebot) =>
(blockId: string): string[] => {
const previousBlocks = typebot.edges
.map((edge) =>
edge.to.blockId === blockId ? edge.from.blockId : undefined
)
.filter(isDefined)
return previousBlocks.concat(
previousBlocks.flatMap(getPreviousBlocks(typebot))
)
}

export default handler
Loading

2 comments on commit fd822a3

@vercel
Copy link

@vercel vercel bot commented on fd822a3 Feb 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

app.typebot.io
builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on fd822a3 Feb 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.