🧐 Add exportResults script
This commit is contained in:
@@ -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 =
|
||||
({
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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[]) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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']
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user