🐛 (results) Fix bug preventing user from seeing linked typebots results
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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[]
|
||||||
|
|||||||
@@ -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),
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 />
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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.',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user