2
0

🧐 Add exportResults script

This commit is contained in:
Baptiste Arnaud
2024-01-12 10:16:01 +01:00
parent 5d088b1e64
commit 69b113fc85
32 changed files with 319 additions and 332 deletions

View File

@@ -7,10 +7,10 @@ import {
TypebotLinkBlock,
} from '@typebot.io/schemas'
import { isInputBlock, byId, isNotDefined } from '@typebot.io/lib'
import { parseResultHeader } from '@typebot.io/lib/results'
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 =
({

View File

@@ -2,17 +2,18 @@ import { useToast } from '@/hooks/useToast'
import {
ResultHeaderCell,
ResultWithAnswers,
TableData,
Typebot,
} from '@typebot.io/schemas'
import { createContext, ReactNode, useContext, useMemo } from 'react'
import { parseResultHeader } from '@typebot.io/lib/results'
import { useTypebot } from '../editor/providers/TypebotProvider'
import { useResultsQuery } from './hooks/useResultsQuery'
import { TableData } from './types'
import { convertResultsToTableData } from './helpers/convertResultsToTableData'
import { trpc } from '@/lib/trpc'
import { isDefined } from '@typebot.io/lib/utils'
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
import { parseResultHeader } from '@typebot.io/lib/results/parseResultHeader'
import { convertResultsToTableData } from '@typebot.io/lib/results/convertResultsToTableData'
import { parseCellContent } from './helpers/parseCellContent'
const resultsContext = createContext<{
resultsList: { results: ResultWithAnswers[] }[] | undefined
@@ -94,7 +95,8 @@ export const ResultsProvider = ({
publishedTypebot
? convertResultsToTableData(
data?.flatMap((d) => d.results) ?? [],
resultHeader
resultHeader,
parseCellContent
)
: [],
[publishedTypebot, data, resultHeader]

View File

@@ -14,7 +14,7 @@ import React from 'react'
import { byId, isDefined } from '@typebot.io/lib'
import { HeaderIcon } from './HeaderIcon'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { parseColumnOrder } from '../helpers/parseColumnsOrder'
import { parseColumnsOrder } from '@typebot.io/lib/results/parseColumnsOrder'
type Props = {
resultId: string | null
@@ -28,7 +28,7 @@ export const ResultModal = ({ resultId, onClose }: Props) => {
? tableData.find((data) => data.id.plainText === resultId)
: undefined
const columnsOrder = parseColumnOrder(
const columnsOrder = parseColumnsOrder(
typebot?.resultsTablePreferences?.columnsOrder,
resultHeader
)

View File

@@ -2,7 +2,7 @@ import { chakra, Fade, Button, useColorModeValue } from '@chakra-ui/react'
import { Cell as CellProps, flexRender } from '@tanstack/react-table'
import { ExpandIcon } from '@/components/icons'
import { memo } from 'react'
import { TableData } from '../../types'
import { TableData } from '@typebot.io/schemas'
type Props = {
cell: CellProps<TableData, unknown>

View File

@@ -20,10 +20,11 @@ import {
import { TRPCError } from '@trpc/server'
import { unparse } from 'papaparse'
import { useState } from 'react'
import { parseResultHeader } from '@typebot.io/lib/results'
import { parseResultHeader } from '@typebot.io/lib/results/parseResultHeader'
import { convertResultsToTableData } from '@typebot.io/lib/results/convertResultsToTableData'
import { parseColumnsOrder } from '@typebot.io/lib/results/parseColumnsOrder'
import { parseUniqueKey } from '@typebot.io/lib/parseUniqueKey'
import { useResults } from '../../ResultsProvider'
import { parseColumnOrder } from '../../helpers/parseColumnsOrder'
import { convertResultsToTableData } from '../../helpers/convertResultsToTableData'
import { byId, isDefined } from '@typebot.io/lib'
import { Typebot } from '@typebot.io/schemas'
@@ -101,7 +102,7 @@ export const ExportAllResultsModal = ({ isOpen, onClose }: Props) => {
const dataToUnparse = convertResultsToTableData(results, resultHeader)
const headerIds = parseColumnOrder(
const headerIds = parseColumnsOrder(
typebot?.resultsTablePreferences?.columnsOrder,
resultHeader
).reduce<string[]>((currentHeaderIds, columnId) => {
@@ -182,12 +183,3 @@ export const ExportAllResultsModal = ({ isOpen, onClose }: Props) => {
</Modal>
)
}
export const parseUniqueKey = (
key: string,
existingKeys: string[],
count = 0
): string => {
if (!existingKeys.includes(key)) return key
return parseUniqueKey(`${key} (${count + 1})`, existingKeys, count + 1)
}

View File

@@ -1,8 +1,8 @@
import { colors } from '@/lib/theme'
import { Box, BoxProps, chakra, useColorModeValue } from '@chakra-ui/react'
import { flexRender, HeaderGroup } from '@tanstack/react-table'
import { TableData } from '@typebot.io/schemas'
import React from 'react'
import { TableData } from '../../types'
type Props = {
headerGroup: HeaderGroup<TableData>

View File

@@ -8,7 +8,12 @@ import {
useColorModeValue,
} from '@chakra-ui/react'
import { AlignLeftTextIcon } from '@/components/icons'
import { ResultHeaderCell, ResultsTablePreferences } from '@typebot.io/schemas'
import {
CellValueType,
ResultHeaderCell,
ResultsTablePreferences,
TableData,
} from '@typebot.io/schemas'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { LoadingRows } from './LoadingRows'
import {
@@ -22,11 +27,10 @@ import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { SelectionToolbar } from './SelectionToolbar'
import { Row } from './Row'
import { HeaderRow } from './HeaderRow'
import { CellValueType, TableData } from '../../types'
import { IndeterminateCheckbox } from './IndeterminateCheckbox'
import { colors } from '@/lib/theme'
import { parseColumnOrder } from '../../helpers/parseColumnsOrder'
import { HeaderIcon } from '../HeaderIcon'
import { parseColumnsOrder } from '@typebot.io/lib/results/parseColumnsOrder'
type ResultsTableProps = {
resultHeader: ResultHeaderCell[]
@@ -60,7 +64,7 @@ export const ResultsTable = ({
columnsWidth = {},
} = {
...preferences,
columnsOrder: parseColumnOrder(preferences?.columnsOrder, resultHeader),
columnsOrder: parseColumnsOrder(preferences?.columnsOrder, resultHeader),
}
const changeColumnOrder = (newColumnOrder: string[]) => {

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react'
import { Row as RowProps } from '@tanstack/react-table'
import Cell from './Cell'
import { TableData } from '../../types'
import { TableData } from '@typebot.io/schemas'
type Props = {
row: RowProps<TableData>

View File

@@ -14,9 +14,9 @@ import React, { useState } from 'react'
import { useToast } from '@/hooks/useToast'
import { useResults } from '../../ResultsProvider'
import { trpc } from '@/lib/trpc'
import { parseColumnOrder } from '../../helpers/parseColumnsOrder'
import { byId } from '@typebot.io/lib/utils'
import { parseUniqueKey } from './ExportAllResultsModal'
import { parseColumnsOrder } from '@typebot.io/lib/results/parseColumnsOrder'
import { parseUniqueKey } from '@typebot.io/lib/parseUniqueKey'
type Props = {
selectedResultsId: string[]
@@ -70,7 +70,7 @@ export const SelectionToolbar = ({
selectedResultsId.includes(data.id.plainText)
)
const headerIds = parseColumnOrder(
const headerIds = parseColumnsOrder(
typebot?.resultsTablePreferences?.columnsOrder,
resultHeader
)

View File

@@ -1,7 +0,0 @@
export const convertDateToReadable = (date: Date): string =>
date.toDateString().split(' ').slice(1, 3).join(' ') +
', ' +
date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})

View File

@@ -1,80 +0,0 @@
import { Stack, Text } from '@chakra-ui/react'
import { isDefined } from '@typebot.io/lib'
import {
ResultWithAnswers,
ResultHeaderCell,
VariableWithValue,
Answer,
} from '@typebot.io/schemas'
import { FileLinks } from '../components/FileLinks'
import { TableData } from '../types'
import { convertDateToReadable } from './convertDateToReadable'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
export const convertResultsToTableData = (
results: ResultWithAnswers[] | undefined,
headerCells: ResultHeaderCell[]
): TableData[] =>
(results ?? []).map((result) => ({
id: { plainText: result.id },
date: {
plainText: convertDateToReadable(result.createdAt),
},
...[...result.answers, ...result.variables].reduce<{
[key: string]: { element?: JSX.Element; plainText: string }
}>((tableData, answerOrVariable) => {
if ('groupId' in answerOrVariable) {
const answer = answerOrVariable satisfies Answer
const header = answer.variableId
? headerCells.find((headerCell) =>
headerCell.variableIds?.includes(answer.variableId as string)
)
: headerCells.find((headerCell) =>
headerCell.blocks?.some((block) => block.id === answer.blockId)
)
if (!header || !header.blocks || !header.blockType) return tableData
const variableValue = result.variables.find(
(variable) => variable.id === answer.variableId
)?.value
const content = variableValue ?? answer.content
return {
...tableData,
[header.id]: parseCellContent(content, header.blockType),
}
}
const variable = answerOrVariable satisfies VariableWithValue
if (variable.value === null) return tableData
const headerId = headerCells.find((headerCell) =>
headerCell.variableIds?.includes(variable.id)
)?.id
if (!headerId) return tableData
if (isDefined(tableData[headerId])) return tableData
return {
...tableData,
[headerId]: parseCellContent(variable.value),
}
}, {}),
}))
const parseCellContent = (
content: VariableWithValue['value'],
blockType?: InputBlockType
): { element?: JSX.Element; plainText: string } => {
if (!content) return { element: undefined, plainText: '' }
if (Array.isArray(content))
return {
element: (
<Stack spacing={2}>
{content.map((item, idx) => (
<Text key={idx}>
{idx + 1}. {item}
</Text>
))}
</Stack>
),
plainText: content.join(', '),
}
return blockType === InputBlockType.FILE
? { element: <FileLinks fileNamesStr={content} />, plainText: content }
: { plainText: content.toString() }
}

View File

@@ -0,0 +1,27 @@
import { Stack, Text } from '@chakra-ui/react'
import { VariableWithValue } from '@typebot.io/schemas'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { FileLinks } from '../components/FileLinks'
export const parseCellContent = (
content: VariableWithValue['value'],
blockType?: InputBlockType
): { element?: React.JSX.Element; plainText: string } => {
if (!content) return { element: undefined, plainText: '' }
if (Array.isArray(content))
return {
element: (
<Stack spacing={2}>
{content.map((item, idx) => (
<Text key={idx}>
{idx + 1}. {item}
</Text>
))}
</Stack>
),
plainText: content.join(', '),
}
return blockType === InputBlockType.FILE
? { element: <FileLinks fileNamesStr={content} />, plainText: content }
: { plainText: content.toString() }
}

View File

@@ -1,15 +0,0 @@
import { ResultHeaderCell } from '@typebot.io/schemas'
export const parseColumnOrder = (
existingOrder: string[] | undefined,
resultHeader: ResultHeaderCell[]
) =>
existingOrder
? [
...existingOrder.slice(0, -1),
...resultHeader
.filter((header) => !existingOrder.includes(header.id))
.map((h) => h.id),
'logs',
]
: ['select', ...resultHeader.map((h) => h.id), 'logs']

View File

@@ -2,9 +2,3 @@ export type HeaderCell = {
Header: JSX.Element
accessor: string
}
export type CellValueType = { element?: JSX.Element; plainText: string }
export type TableData = {
id: Pick<CellValueType, 'plainText'>
} & Record<string, CellValueType>

View File

@@ -4052,6 +4052,10 @@
"type": "string",
"nullable": true
},
"customChatsLimit": {
"type": "number",
"nullable": true
},
"customSeatsLimit": {
"type": "number",
"nullable": true
@@ -4075,6 +4079,7 @@
"icon",
"plan",
"stripeId",
"customChatsLimit",
"customSeatsLimit",
"isSuspended",
"isPastDue",

View File

@@ -16,7 +16,7 @@ import {
isWebhookBlock,
omit,
} from '@typebot.io/lib'
import { parseAnswers } from '@typebot.io/lib/results'
import { parseAnswers } from '@typebot.io/lib/results/parseAnswers'
import { initMiddleware, methodNotAllowed, notFound } from '@typebot.io/lib/api'
import { stringify } from 'qs'
import Cors from 'cors'

View File

@@ -7,7 +7,7 @@ import {
import { NextApiRequest, NextApiResponse } from 'next'
import { createTransport, getTestMessageUrl } from 'nodemailer'
import { isDefined, isEmpty, isNotDefined, omit } from '@typebot.io/lib'
import { parseAnswers } from '@typebot.io/lib/results'
import { parseAnswers } from '@typebot.io/lib/results/parseAnswers'
import { methodNotAllowed, initMiddleware } from '@typebot.io/lib/api'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'