2
0

🐛 (results) Fix bug preventing user from seeing linked typebots results

This commit is contained in:
Baptiste Arnaud
2022-11-06 09:57:08 +01:00
parent 63845effaf
commit 6dd7bd9562
18 changed files with 234 additions and 151 deletions

View File

@@ -11,8 +11,8 @@ import {
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { useResults } from 'contexts/ResultsProvider' import { useResults } from 'contexts/ResultsProvider'
import React from 'react' import React from 'react'
import { HeaderIcon } from 'services/typebots/results'
import { isDefined } from 'utils' import { isDefined } from 'utils'
import { HeaderIcon } from './ResultsTable/ResultsTable'
type Props = { type Props = {
resultIdx: number | null resultIdx: number | null

View File

@@ -27,7 +27,7 @@ export const ResultsContent = () => {
const handleResultModalClose = () => setExpandedResultIndex(null) const handleResultModalClose = () => setExpandedResultIndex(null)
const handleLogOpenIndex = (index: number) => () => { const handleLogOpenIndex = (index: number) => () => {
if (!results) return if (!results[index]) return
setInspectingLogsResultId(results[index].id) setInspectingLogsResultId(results[index].id)
} }

View File

@@ -15,7 +15,6 @@ import { ToolIcon, EyeIcon, EyeOffIcon, GripIcon } from 'assets/icons'
import { ResultHeaderCell } from 'models' import { ResultHeaderCell } from 'models'
import React, { forwardRef, useState } from 'react' import React, { forwardRef, useState } from 'react'
import { isNotDefined } from 'utils' import { isNotDefined } from 'utils'
import { HeaderIcon } from './ResultsTable'
import { import {
DndContext, DndContext,
closestCenter, closestCenter,
@@ -35,6 +34,7 @@ import {
useSortable, useSortable,
arrayMove, arrayMove,
} from '@dnd-kit/sortable' } from '@dnd-kit/sortable'
import { HeaderIcon } from 'services/typebots/results'
type Props = { type Props = {
resultHeader: ResultHeaderCell[] resultHeader: ResultHeaderCell[]

View File

@@ -93,18 +93,39 @@ export const ResultsActionButtons = ({
: tableData.filter((data) => : tableData.filter((data) =>
selectedResultsId.includes(data.id.plainText) selectedResultsId.includes(data.id.plainText)
) )
const fields = typebot?.resultsTablePreferences?.columnsOrder
? typebot.resultsTablePreferences.columnsOrder.reduce<string[]>(
(currentHeaderLabels, columnId) => {
if (
typebot.resultsTablePreferences?.columnsVisibility[columnId] ===
false
)
return currentHeaderLabels
const columnLabel = resultHeader.find(
(headerCell) => headerCell.id === columnId
)?.label
if (!columnLabel) return currentHeaderLabels
return [...currentHeaderLabels, columnLabel]
},
[]
)
: resultHeader.map((headerCell) => headerCell.label)
const data = dataToUnparse.map<{ [key: string]: string }>((data) => {
const newObject: { [key: string]: string } = {}
fields?.forEach((field) => {
console.log(data[field])
newObject[field] = data[field]?.plainText
})
return newObject
})
const csvData = new Blob( const csvData = new Blob(
[ [
unparse({ unparse({
data: dataToUnparse.map<{ [key: string]: string }>((data) => { data,
const newObject: { [key: string]: string } = {} fields,
Object.keys(data).forEach((key) => {
newObject[key] = (data[key] as { plainText: string })
.plainText as string
})
return newObject
}),
fields: resultHeader.map((h) => h.label),
}), }),
], ],
{ {

View File

@@ -8,7 +8,7 @@ import {
Stack, Stack,
Text, Text,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { AlignLeftTextIcon, CalendarIcon, CodeIcon } from 'assets/icons' import { AlignLeftTextIcon } from 'assets/icons'
import { ResultHeaderCell, ResultsTablePreferences } from 'models' import { ResultHeaderCell, ResultsTablePreferences } from 'models'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { LoadingRows } from './LoadingRows' import { LoadingRows } from './LoadingRows'
@@ -18,14 +18,13 @@ import {
ColumnOrderState, ColumnOrderState,
ColumnDef, ColumnDef,
} from '@tanstack/react-table' } from '@tanstack/react-table'
import { BlockIcon } from 'components/editor/BlocksSideBar/BlockIcon'
import { ColumnSettingsButton } from './ColumnsSettingsButton' import { ColumnSettingsButton } from './ColumnsSettingsButton'
import { useTypebot } from 'contexts/TypebotContext' import { useTypebot } from 'contexts/TypebotContext'
import { useDebounce } from 'use-debounce' import { useDebounce } from 'use-debounce'
import { ResultsActionButtons } from './ResultsActionButtons' import { ResultsActionButtons } from './ResultsActionButtons'
import { Row } from './Row' import { Row } from './Row'
import { HeaderRow } from './HeaderRow' import { HeaderRow } from './HeaderRow'
import { CellValueType, TableData } from 'services/typebots/results' import { CellValueType, HeaderIcon, TableData } from 'services/typebots/results'
type ResultsTableProps = { type ResultsTableProps = {
resultHeader: ResultHeaderCell[] resultHeader: ResultHeaderCell[]
@@ -112,7 +111,7 @@ export const ResultsTable = ({
...resultHeader.map<ColumnDef<TableData>>((header) => ({ ...resultHeader.map<ColumnDef<TableData>>((header) => ({
id: header.id, id: header.id,
accessorKey: header.label, accessorKey: header.label,
size: header.isLong ? 400 : 200, size: 200,
header: () => ( header: () => (
<HStack overflow="hidden" data-testid={`${header.label} header`}> <HStack overflow="hidden" data-testid={`${header.label} header`}>
<HeaderIcon header={header} /> <HeaderIcon header={header} />
@@ -147,8 +146,7 @@ export const ResultsTable = ({
), ),
}, },
], ],
// eslint-disable-next-line react-hooks/exhaustive-deps [onLogOpenIndex, resultHeader]
[resultHeader]
) )
const instance = useReactTable({ const instance = useReactTable({
@@ -258,12 +256,3 @@ const IndeterminateCheckbox = React.forwardRef(
) )
} }
) )
export const HeaderIcon = ({ header }: { header: ResultHeaderCell }) =>
header.blockType ? (
<BlockIcon type={header.blockType} />
) : header.variableId ? (
<CodeIcon />
) : (
<CalendarIcon />
)

View File

@@ -29,12 +29,11 @@ export const EditableUrl = ({
const [value, setValue] = useState(pathname) const [value, setValue] = useState(pathname)
const handleSubmit = (newPathname: string) => { const handleSubmit = (newPathname: string) => {
if (/^[a-z]+(-[a-z]+)*$/.test(newPathname)) if (/^[a-z0-9-]*$/.test(newPathname)) return onPathnameChange(newPathname)
return onPathnameChange(newPathname)
setValue(pathname) setValue(pathname)
showToast({ showToast({
title: 'Invalid ID', title: 'Invalid ID',
description: 'Should contain only contain letters and dashes.', description: 'Should contain only contain letters, numbers and dashes.',
}) })
} }

View File

@@ -6,7 +6,7 @@ import {
useResults as useFetchResults, useResults as useFetchResults,
} from 'services/typebots/results' } from 'services/typebots/results'
import { KeyedMutator } from 'swr' import { KeyedMutator } from 'swr'
import { isDefined, parseResultHeader } from 'utils' import { parseResultHeader } from 'utils'
import { useTypebot } from './TypebotContext' import { useTypebot } from './TypebotContext'
const resultsContext = createContext<{ const resultsContext = createContext<{
@@ -46,19 +46,17 @@ export const ResultsProvider = ({
typebotId, typebotId,
}) })
console.log(data?.flatMap((d) => d.results) ?? [])
const fetchMore = () => setSize((state) => state + 1) const fetchMore = () => setSize((state) => state + 1)
const groupsAndVariables = { const resultHeader = useMemo(
groups: [ () =>
...(publishedTypebot?.groups ?? []), publishedTypebot
...(linkedTypebots?.flatMap((t) => t.groups) ?? []), ? parseResultHeader(publishedTypebot, linkedTypebots)
].filter(isDefined), : [],
variables: [ [linkedTypebots, publishedTypebot]
...(publishedTypebot?.variables ?? []), )
...(linkedTypebots?.flatMap((t) => t.variables) ?? []),
].filter(isDefined),
}
const resultHeader = parseResultHeader(groupsAndVariables)
const tableData = useMemo( const tableData = useMemo(
() => () =>
@@ -68,8 +66,7 @@ export const ResultsProvider = ({
resultHeader resultHeader
) )
: [], : [],
// eslint-disable-next-line react-hooks/exhaustive-deps [publishedTypebot, data, resultHeader]
[publishedTypebot?.id, resultHeader.length, data]
) )
return ( return (

View File

@@ -45,7 +45,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
], ],
}, },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
select: { name: true, id: true, groups: true }, select: { name: true, id: true, groups: true, variables: true },
}) })
return res.send({ typebots }) return res.send({ typebots })
} }

View File

@@ -228,7 +228,7 @@ test('should display invoices', async ({ page }) => {
await page.click('text=Billing & Usage') await page.click('text=Billing & Usage')
await expect(page.locator('text="Invoices"')).toBeVisible() await expect(page.locator('text="Invoices"')).toBeVisible()
await expect(page.locator('tr')).toHaveCount(3) await expect(page.locator('tr')).toHaveCount(3)
await expect(page.locator('text="39.00"')).toBeVisible() await expect(page.locator('text="$39.00"')).toBeVisible()
}) })
test('custom plans should work', async ({ page }) => { test('custom plans should work', async ({ page }) => {

View File

@@ -21,7 +21,7 @@ test.beforeEach(async () => {
await injectFakeResults({ typebotId, count: 200, isChronological: true }) await injectFakeResults({ typebotId, count: 200, isChronological: true })
}) })
test('Results', async ({ page }) => { test('table features should work', async ({ page }) => {
await page.goto(`/typebots/${typebotId}/results`) await page.goto(`/typebots/${typebotId}/results`)
await test.step('Check header format', async () => { await test.step('Check header format', async () => {
@@ -147,14 +147,14 @@ test('Results', async ({ page }) => {
const validateExportSelection = (data: unknown[]) => { const validateExportSelection = (data: unknown[]) => {
expect(data).toHaveLength(3) expect(data).toHaveLength(3)
expect((data[1] as unknown[])[3]).toBe('content199') expect((data[1] as unknown[])[0]).toBe('content199')
expect((data[2] as unknown[])[3]).toBe('content198') expect((data[2] as unknown[])[0]).toBe('content198')
} }
const validateExportAll = (data: unknown[]) => { const validateExportAll = (data: unknown[]) => {
expect(data).toHaveLength(201) expect(data).toHaveLength(201)
expect((data[1] as unknown[])[3]).toBe('content199') expect((data[1] as unknown[])[0]).toBe('content199')
expect((data[200] as unknown[])[3]).toBe('content0') expect((data[200] as unknown[])[0]).toBe('content0')
} }
const scrollToBottom = (page: Page) => const scrollToBottom = (page: Page) =>

View File

@@ -73,7 +73,7 @@ export const canPublishFileInput = async ({
return false return false
} }
if (workspace?.plan === Plan.FREE) { if (workspace?.plan === Plan.FREE) {
forbidden(res, 'You need to upgrade your plan to use this feature') forbidden(res, 'You need to upgrade your plan to use file input blocks')
return false return false
} }
return true return true

View File

@@ -136,7 +136,7 @@ export const parseSubmissionsColumns = (
): HeaderCell[] => ): HeaderCell[] =>
resultHeader.map((header) => ({ resultHeader.map((header) => ({
Header: ( Header: (
<HStack minW={header.isLong ? '400px' : '150px'} maxW="500px"> <HStack minW="150px" maxW="500px">
<HeaderIcon header={header} /> <HeaderIcon header={header} />
<Text>{header.label}</Text> <Text>{header.label}</Text>
</HStack> </HStack>
@@ -144,10 +144,10 @@ export const parseSubmissionsColumns = (
accessor: header.label, accessor: header.label,
})) }))
const HeaderIcon = ({ header }: { header: ResultHeaderCell }) => export const HeaderIcon = ({ header }: { header: ResultHeaderCell }) =>
header.blockType ? ( header.blockType ? (
<BlockIcon type={header.blockType} /> <BlockIcon type={header.blockType} />
) : header.variableId ? ( ) : header.variableIds ? (
<CodeIcon /> <CodeIcon />
) : ( ) : (
<CalendarIcon /> <CalendarIcon />
@@ -174,9 +174,13 @@ export const convertResultsToTableData = (
if ('groupId' in answerOrVariable) { if ('groupId' in answerOrVariable) {
const answer = answerOrVariable as Answer const answer = answerOrVariable as Answer
const header = answer.variableId const header = answer.variableId
? headerCells.find((h) => h.variableId === answer.variableId) ? headerCells.find((headerCell) =>
: headerCells.find((h) => h.blockId === answer.blockId) headerCell.variableIds?.includes(answer.variableId as string)
if (!header || !header.blockId || !header.blockType) return o )
: headerCells.find((headerCell) =>
headerCell.blocks?.some((block) => block.id === answer.blockId)
)
if (!header || !header.blocks || !header.blockType) return o
return { return {
...o, ...o,
[header.label]: { [header.label]: {
@@ -186,7 +190,9 @@ export const convertResultsToTableData = (
} }
} }
const variable = answerOrVariable as VariableWithValue const variable = answerOrVariable as VariableWithValue
const key = headerCells.find((h) => h.variableId === variable.id)?.label const key = headerCells.find((headerCell) =>
headerCell.variableIds?.includes(variable.id)
)?.label
if (!key) return o if (!key) return o
if (isDefined(o[key])) return o if (isDefined(o[key])) return o
return { return {

View File

@@ -203,16 +203,7 @@ const getBodyContent =
return body === '{{state}}' return body === '{{state}}'
? JSON.stringify( ? JSON.stringify(
resultValues resultValues
? parseAnswers({ ? parseAnswers(typebot, linkedTypebots)(resultValues)
groups: [
...typebot.groups,
...linkedTypebots.flatMap((t) => t.groups),
],
variables: [
...typebot.variables,
...linkedTypebots.flatMap((t) => t.variables),
],
})(resultValues)
: await parseSampleResult(typebot, linkedTypebots)(groupId) : await parseSampleResult(typebot, linkedTypebots)(groupId)
) )
: body : body

View File

@@ -180,13 +180,7 @@ const getEmailBody = async ({
})) as unknown as PublicTypebot })) as unknown as PublicTypebot
if (!typebot) return if (!typebot) return
const linkedTypebots = await getLinkedTypebots(typebot) const linkedTypebots = await getLinkedTypebots(typebot)
const answers = parseAnswers({ const answers = parseAnswers(typebot, linkedTypebots)(resultValues)
groups: [...typebot.groups, ...linkedTypebots.flatMap((t) => t.groups)],
variables: [
...typebot.variables,
...linkedTypebots.flatMap((t) => t.variables),
],
})(resultValues)
return { return {
html: render( html: render(
<DefaultBotNotificationEmail <DefaultBotNotificationEmail

View File

@@ -26,8 +26,8 @@ test('can list typebots', async ({ request }) => {
const response = await request.get(`/api/typebots`, { const response = await request.get(`/api/typebots`, {
headers: { Authorization: `Bearer ${apiToken}` }, headers: { Authorization: `Bearer ${apiToken}` },
}) })
const { typebots } = await response.json() const { typebots } = (await response.json()) as { typebots: unknown[] }
expect(typebots).toHaveLength(1) expect(typebots.length).toBeGreaterThanOrEqual(1)
expect(typebots[0]).toMatchObject({ expect(typebots[0]).toMatchObject({
id: typebotId, id: typebotId,
publishedTypebotId: null, publishedTypebotId: null,

View File

@@ -18,13 +18,7 @@ export const parseSampleResult =
async ( async (
currentGroupId: string currentGroupId: string
): Promise<Record<string, string | boolean | undefined>> => { ): Promise<Record<string, string | boolean | undefined>> => {
const header = parseResultHeader({ const header = parseResultHeader(typebot, linkedTypebots)
groups: [...typebot.groups, ...linkedTypebots.flatMap((t) => t.groups)],
variables: [
...typebot.variables,
...linkedTypebots.flatMap((t) => t.variables),
],
})
const linkedInputBlocks = await extractLinkedInputBlocks( const linkedInputBlocks = await extractLinkedInputBlocks(
typebot, typebot,
linkedTypebots linkedTypebots
@@ -33,7 +27,7 @@ export const parseSampleResult =
return { return {
message: 'This is a sample result, it has been generated ⬇️', message: 'This is a sample result, it has been generated ⬇️',
'Submitted at': new Date().toISOString(), 'Submitted at': new Date().toISOString(),
...parseGroupsResultSample(linkedInputBlocks, header), ...parseResultSample(linkedInputBlocks, header),
} }
} }
@@ -81,24 +75,26 @@ const extractLinkedInputBlocks =
).concat(linkedBotInputs.flatMap((l) => l)) ).concat(linkedBotInputs.flatMap((l) => l))
} }
const parseGroupsResultSample = ( const parseResultSample = (
inputBlocks: InputBlock[], inputBlocks: InputBlock[],
header: ResultHeaderCell[] headerCells: ResultHeaderCell[]
) => ) =>
header.reduce<Record<string, string | boolean | undefined>>( headerCells.reduce<Record<string, string | boolean | undefined>>(
(blocks, cell) => { (resultSample, cell) => {
const inputBlock = inputBlocks.find((block) => block.id === cell.blockId) const inputBlock = inputBlocks.find((inputBlock) =>
cell.blocks?.some((block) => block.id === inputBlock.id)
)
if (isNotDefined(inputBlock)) { if (isNotDefined(inputBlock)) {
if (cell.variableId) if (cell.variableIds)
return { return {
...blocks, ...resultSample,
[cell.label]: 'content', [cell.label]: 'content',
} }
return blocks return resultSample
} }
const value = getSampleValue(inputBlock) const value = getSampleValue(inputBlock)
return { return {
...blocks, ...resultSample,
[cell.label]: value, [cell.label]: value,
} }
}, },

View File

@@ -17,8 +17,10 @@ export type ResultValues = Pick<
export type ResultHeaderCell = { export type ResultHeaderCell = {
id: string id: string
label: string label: string
blockId?: string blocks?: {
id: string
groupId: string
}[]
blockType?: InputBlockType blockType?: InputBlockType
isLong?: boolean variableIds?: string[]
variableId?: string
} }

View File

@@ -6,90 +6,173 @@ import {
ResultWithAnswers, ResultWithAnswers,
Answer, Answer,
VariableWithValue, VariableWithValue,
Typebot,
} from 'models' } from 'models'
import { isInputBlock, isDefined, byId } from './utils' import { isInputBlock, isDefined, byId } from './utils'
export const parseResultHeader = ({ export const parseResultHeader = (
groups, typebot: Pick<Typebot, 'groups' | 'variables'>,
variables, linkedTypebots: Pick<Typebot, 'groups' | 'variables'>[] | undefined
}: { ): ResultHeaderCell[] => {
groups: Group[] const parsedGroups = [
variables: Variable[] ...typebot.groups,
}): ResultHeaderCell[] => { ...(linkedTypebots ?? []).flatMap((linkedTypebot) => linkedTypebot.groups),
const parsedGroups = parseInputsResultHeader({ groups, variables }) ]
const parsedVariables = [
...typebot.variables,
...(linkedTypebots ?? []).flatMap(
(linkedTypebot) => linkedTypebot.variables
),
]
const inputsResultHeader = parseInputsResultHeader({
groups: parsedGroups,
variables: parsedVariables,
})
return [ return [
{ label: 'Submitted at', id: 'date' }, { label: 'Submitted at', id: 'date' },
...parsedGroups, ...inputsResultHeader,
...parseVariablesHeaders(variables, parsedGroups), ...parseVariablesHeaders(parsedVariables, inputsResultHeader),
] ]
} }
type ResultHeaderCellWithBlock = Omit<ResultHeaderCell, 'blocks'> & {
blocks: NonNullable<ResultHeaderCell['blocks']>
}
const parseInputsResultHeader = ({ const parseInputsResultHeader = ({
groups, groups,
variables, variables,
}: { }: {
groups: Group[] groups: Group[]
variables: Variable[] variables: Variable[]
}): ResultHeaderCell[] => }): ResultHeaderCellWithBlock[] =>
( (
groups groups
.flatMap((g) => .flatMap((group) =>
g.blocks.map((s) => ({ group.blocks.map((block) => ({
...s, ...block,
blockTitle: g.title, groupTitle: group.title,
})) }))
) )
.filter((block) => isInputBlock(block)) as (InputBlock & { .filter((block) => isInputBlock(block)) as (InputBlock & {
blockTitle: string groupTitle: string
})[] })[]
).reduce<ResultHeaderCell[]>((headers, inputBlock) => { ).reduce<ResultHeaderCellWithBlock[]>((existingHeaders, inputBlock) => {
if ( if (
headers.find( existingHeaders.some(
(h) => (existingHeader) =>
isDefined(h.variableId) && inputBlock.options.variableId &&
h.variableId === existingHeader.variableIds?.includes(inputBlock.options.variableId)
variables.find(byId(inputBlock.options.variableId))?.id
) )
) )
return headers return existingHeaders
const matchedVariableName = const matchedVariableName =
inputBlock.options.variableId && inputBlock.options.variableId &&
variables.find(byId(inputBlock.options.variableId))?.name variables.find(byId(inputBlock.options.variableId))?.name
let label = matchedVariableName ?? inputBlock.blockTitle let label = matchedVariableName ?? inputBlock.groupTitle
const totalPrevious = headers.filter((h) => h.label.includes(label)).length const existingHeader = existingHeaders.find((h) => h.label === label)
if (totalPrevious > 0) label = label + ` (${totalPrevious})` if (existingHeader) {
return [ if (
...headers, existingHeader.blocks?.some(
{ (block) => block.groupId === inputBlock.groupId
id: inputBlock.id, )
blockType: inputBlock.type, ) {
blockId: inputBlock.id, const totalPrevious = existingHeaders.filter((h) =>
variableId: inputBlock.options.variableId, h.label.includes(label)
label, ).length
isLong: 'isLong' in inputBlock.options && inputBlock.options.isLong, const newHeaderCell: ResultHeaderCellWithBlock = {
}, id: inputBlock.id,
] label: label + ` (${totalPrevious})`,
blocks: [
{
id: inputBlock.id,
groupId: inputBlock.groupId,
},
],
blockType: inputBlock.type,
variableIds: inputBlock.options.variableId
? [inputBlock.options.variableId]
: undefined,
}
return [...existingHeaders, newHeaderCell]
}
const updatedHeaderCell: ResultHeaderCellWithBlock = {
...existingHeader,
variableIds:
existingHeader.variableIds && inputBlock.options.variableId
? existingHeader.variableIds.concat([inputBlock.options.variableId])
: undefined,
blocks: existingHeader.blocks.concat({
id: inputBlock.id,
groupId: inputBlock.groupId,
}),
}
return [
...existingHeaders.filter(
(existingHeader) => existingHeader.label !== label
),
updatedHeaderCell,
]
}
const newHeaderCell: ResultHeaderCellWithBlock = {
id: inputBlock.id,
label,
blocks: [
{
id: inputBlock.id,
groupId: inputBlock.groupId,
},
],
blockType: inputBlock.type,
variableIds: inputBlock.options.variableId
? [inputBlock.options.variableId]
: undefined,
}
return [...existingHeaders, newHeaderCell]
}, []) }, [])
const parseVariablesHeaders = ( const parseVariablesHeaders = (
variables: Variable[], variables: Variable[],
blockResultHeader: ResultHeaderCell[] existingInputResultHeaders: ResultHeaderCell[]
) => ) =>
variables.reduce<ResultHeaderCell[]>((headers, v) => { variables.reduce<ResultHeaderCell[]>((existingHeaders, variable) => {
if (blockResultHeader.find((h) => h.variableId === v.id)) return headers if (
return [ existingInputResultHeaders.some((existingInputResultHeader) =>
...headers, existingInputResultHeader.variableIds?.includes(variable.id)
{ )
id: v.id, )
label: v.name, return existingHeaders
variableId: v.id,
}, const headerCellWithSameLabel = existingHeaders.find(
] (existingHeader) => existingHeader.label === variable.name
)
if (headerCellWithSameLabel) {
const updatedHeaderCell: ResultHeaderCell = {
...headerCellWithSameLabel,
variableIds: headerCellWithSameLabel.variableIds?.concat([variable.id]),
}
return [
...existingHeaders.filter((h) => h.label !== variable.name),
updatedHeaderCell,
]
}
const newHeaderCell: ResultHeaderCell = {
id: variable.id,
label: variable.name,
variableIds: [variable.id],
}
return [...existingHeaders, newHeaderCell]
}, []) }, [])
export const parseAnswers = export const parseAnswers =
({ groups, variables }: { groups: Group[]; variables: Variable[] }) => (
typebot: Pick<Typebot, 'groups' | 'variables'>,
linkedTypebots: Pick<Typebot, 'groups' | 'variables'>[] | undefined
) =>
({ ({
createdAt, createdAt,
answers, answers,
@@ -97,7 +180,7 @@ export const parseAnswers =
}: Pick<ResultWithAnswers, 'createdAt' | 'answers' | 'variables'>): { }: Pick<ResultWithAnswers, 'createdAt' | 'answers' | 'variables'>): {
[key: string]: string [key: string]: string
} => { } => {
const header = parseResultHeader({ groups, variables }) const header = parseResultHeader(typebot, linkedTypebots)
return { return {
submittedAt: createdAt, submittedAt: createdAt,
...[...answers, ...resultVariables].reduce<{ ...[...answers, ...resultVariables].reduce<{
@@ -106,9 +189,14 @@ export const parseAnswers =
if ('blockId' in answerOrVariable) { if ('blockId' in answerOrVariable) {
const answer = answerOrVariable as Answer const answer = answerOrVariable as Answer
const key = answer.variableId const key = answer.variableId
? header.find((cell) => cell.variableId === answer.variableId) ? header.find(
?.label (cell) =>
: header.find((cell) => cell.blockId === answer.blockId)?.label answer.variableId &&
cell.variableIds?.includes(answer.variableId)
)?.label
: header.find((cell) =>
cell.blocks?.some((block) => block.id === answer.blockId)
)?.label
if (!key) return o if (!key) return o
return { return {
...o, ...o,